From 6edbac86e203ca306b05b707e359e10ffdd43c8d Mon Sep 17 00:00:00 2001 From: MichaelDvP Date: Fri, 24 Apr 2026 14:46:53 +0200 Subject: [PATCH 01/48] fix legegram length, #2969 --- src/devices/thermostat.cpp | 2 +- src/emsesp_version.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/devices/thermostat.cpp b/src/devices/thermostat.cpp index aa909b515..ceba2fc86 100644 --- a/src/devices/thermostat.cpp +++ b/src/devices/thermostat.cpp @@ -173,7 +173,7 @@ Thermostat::Thermostat(uint8_t device_type, uint8_t device_id, uint8_t product_i for (uint8_t i = 0; i < monitor_size; i++) { register_telegram_type(monitor_typeids[i], "RC300Monitor", false, MAKE_PF_CB(process_RC300Monitor), 33); register_telegram_type(set_typeids[i], "RC300Set", false, MAKE_PF_CB(process_RC300Set), 29); - register_telegram_type(summer_typeids[i], "RC300Summer", false, MAKE_PF_CB(process_RC300Summer), 13); + register_telegram_type(summer_typeids[i], "RC300Summer", false, MAKE_PF_CB(process_RC300Summer), 14); register_telegram_type(curve_typeids[i], "RC300Curves", false, MAKE_PF_CB(process_RC300Curve), 9); register_telegram_type(summer2_typeids[i], "RC300Summer2", false, MAKE_PF_CB(process_RC300Summer2), 8); } diff --git a/src/emsesp_version.h b/src/emsesp_version.h index 4909b018f..c98cd567e 100644 --- a/src/emsesp_version.h +++ b/src/emsesp_version.h @@ -1 +1 @@ -#define EMSESP_APP_VERSION "3.8.2-dev.18" +#define EMSESP_APP_VERSION "3.8.2-dev.19" From 112adf9eb063fc7767c7ab02a4f68842a2abf81a Mon Sep 17 00:00:00 2001 From: proddy Date: Sat, 25 Apr 2026 11:19:39 +0200 Subject: [PATCH 02/48] add vscode --- .gitignore | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/.gitignore b/.gitignore index bb18f41fa..743e6590a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,4 @@ -# vscode -.vscode/c_cpp_properties.json -.vscode/extensions.json -.vscode/launch.json + # c++ compiling .clang_complete @@ -63,7 +60,7 @@ words-found-verbose.txt # sonarlint compile_commands.json -# pioarduino + hybrid +# other files managed_components dependencies.lock CMakeLists.txt @@ -75,3 +72,10 @@ pnpm-lock.yaml .cache/ interface/.tsbuildinfo test/test_api/package-lock.json + +# vscode +.vscode/c_cpp_properties.json +.vscode/extensions.json +.vscode/launch.json +.vscode/settings.json +.clangd From 147c09ae64b1dd3249b56f9cf06a762faf72dcc4 Mon Sep 17 00:00:00 2001 From: proddy Date: Sat, 25 Apr 2026 11:42:26 +0200 Subject: [PATCH 03/48] automatically update versions in Cloudflare KV store --- .github/workflows/dev_release.yml | 8 ++++++++ .github/workflows/stable_release.yml | 8 ++++++++ 2 files changed, 16 insertions(+) diff --git a/.github/workflows/dev_release.yml b/.github/workflows/dev_release.yml index e68ac55b4..04365a655 100644 --- a/.github/workflows/dev_release.yml +++ b/.github/workflows/dev_release.yml @@ -77,3 +77,11 @@ jobs: files: | CHANGELOG_LATEST.md ./build/firmware/*.* + + - name: Update version in Cloudflare KV store + run: | + JSON_DATA='{"version": "${{steps.build_info.outputs.VERSION}}", "date": "$(date -u +%Y-%m-%d)"}' + curl -X PUT "https://api.cloudflare.com/client/v4/accounts/${{ secrets.CF_ACCOUNT_ID }}/storage/kv/namespaces/${{ secrets.CF_NAMESPACE_ID }}/values/dev" \ + -H "Authorization: Bearer ${{ secrets.CF_API_TOKEN }}" \ + -H "Content-Type: text/plain" \ + -d "$JSON_DATA" diff --git a/.github/workflows/stable_release.yml b/.github/workflows/stable_release.yml index 6286e6ea3..d18cd52b9 100644 --- a/.github/workflows/stable_release.yml +++ b/.github/workflows/stable_release.yml @@ -61,3 +61,11 @@ jobs: files: | CHANGELOG.md ./build/firmware/*.* + + - name: Update version in Cloudflare KV store + run: | + JSON_DATA='{"version": "${{ github.event.release.tag_name }}", "date": "$(date -u +%Y-%m-%d)"}' + curl -X PUT "https://api.cloudflare.com/client/v4/accounts/${{ secrets.CF_ACCOUNT_ID }}/storage/kv/namespaces/${{ secrets.CF_NAMESPACE_ID }}/values/stable" \ + -H "Authorization: Bearer ${{ secrets.CF_API_TOKEN }}" \ + -H "Content-Type: text/plain" \ + -d "$JSON_DATA" From 7056c446fa77e09582b181776e36ba18f7bab7c5 Mon Sep 17 00:00:00 2001 From: proddy Date: Sat, 25 Apr 2026 20:55:10 +0200 Subject: [PATCH 04/48] use emsesp.org/versions.json --- interface/package.json | 6 +- interface/pnpm-lock.yaml | 250 +++++++++++++-------------- interface/src/api/endpoints.ts | 10 +- interface/src/api/system.ts | 31 +--- interface/src/app/status/Version.tsx | 91 +++++----- interface/vite.config.ts | 2 +- mock-api/package.json | 2 +- mock-api/restServer.ts | 65 ++++--- src/core/system.cpp | 1 - 9 files changed, 223 insertions(+), 235 deletions(-) diff --git a/interface/package.json b/interface/package.json index d2d955b19..ce234abf8 100644 --- a/interface/package.json +++ b/interface/package.json @@ -61,11 +61,11 @@ "eslint-config-prettier": "^10.1.8", "prettier": "^3.8.3", "rollup-plugin-visualizer": "^7.0.1", - "terser": "^5.46.1", + "terser": "^5.46.2", "typescript-eslint": "^8.59.0", - "vite": "^8.0.9", + "vite": "^8.0.10", "vite-plugin-imagemin": "^0.6.1", "vite-tsconfig-paths": "^6.1.1" }, - "packageManager": "pnpm@10.33.1+sha512.05ba3c1d5d1c18f68df06470d74055e62d41fc110a0c660db1b2dfb2785327f04cf0f68345d4609bc52089e7fa0343c31593b2f9594e2c5d5da426230acc9820" + "packageManager": "pnpm@10.33.2+sha512.a90faf6feeab71ad6c6e57f94e0fe1a12f5dcc22cd754db40ae9593eb6a3e0b6b12e3540218bb37ae083404b1f2ce6db2a4121e979829b4aff94b99f49da1cf8" } diff --git a/interface/pnpm-lock.yaml b/interface/pnpm-lock.yaml index 735cfd2c5..5df136395 100644 --- a/interface/pnpm-lock.yaml +++ b/interface/pnpm-lock.yaml @@ -83,7 +83,7 @@ importers: version: 10.0.1(eslint@10.2.1) '@preact/preset-vite': specifier: ^2.10.5 - version: 2.10.5(@babel/core@7.29.0)(preact@10.29.1)(rollup@4.59.0)(vite@8.0.9(@types/node@25.6.0)(esbuild@0.27.4)(terser@5.46.1)) + version: 2.10.5(@babel/core@7.29.0)(preact@10.29.1)(rollup@4.59.0)(vite@8.0.10(@types/node@25.6.0)(esbuild@0.27.4)(terser@5.46.2)) '@trivago/prettier-plugin-sort-imports': specifier: ^6.0.2 version: 6.0.2(prettier@3.8.3) @@ -113,22 +113,22 @@ importers: version: 3.8.3 rollup-plugin-visualizer: specifier: ^7.0.1 - version: 7.0.1(rolldown@1.0.0-rc.16)(rollup@4.59.0) + version: 7.0.1(rolldown@1.0.0-rc.17)(rollup@4.59.0) terser: - specifier: ^5.46.1 - version: 5.46.1 + specifier: ^5.46.2 + version: 5.46.2 typescript-eslint: specifier: ^8.59.0 version: 8.59.0(eslint@10.2.1)(typescript@6.0.3) vite: - specifier: ^8.0.9 - version: 8.0.9(@types/node@25.6.0)(esbuild@0.27.4)(terser@5.46.1) + specifier: ^8.0.10 + version: 8.0.10(@types/node@25.6.0)(esbuild@0.27.4)(terser@5.46.2) vite-plugin-imagemin: specifier: ^0.6.1 - version: 0.6.1(vite@8.0.9(@types/node@25.6.0)(esbuild@0.27.4)(terser@5.46.1)) + version: 0.6.1(vite@8.0.10(@types/node@25.6.0)(esbuild@0.27.4)(terser@5.46.2)) vite-tsconfig-paths: specifier: ^6.1.1 - version: 6.1.1(typescript@6.0.3)(vite@8.0.9(@types/node@25.6.0)(esbuild@0.27.4)(terser@5.46.1)) + version: 6.1.1(typescript@6.0.3)(vite@8.0.10(@types/node@25.6.0)(esbuild@0.27.4)(terser@5.46.2)) packages: @@ -237,11 +237,11 @@ packages: resolution: {integrity: sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==} engines: {node: '>=6.9.0'} - '@emnapi/core@1.9.2': - resolution: {integrity: sha512-UC+ZhH3XtczQYfOlu3lNEkdW/p4dsJ1r/bP7H8+rhao3TTTMO1ATq/4DdIi23XuGoFY+Cz0JmCbdVl0hz9jZcA==} + '@emnapi/core@1.10.0': + resolution: {integrity: sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==} - '@emnapi/runtime@1.9.2': - resolution: {integrity: sha512-3U4+MIWHImeyu1wnmVygh5WlgfYDtyf0k8AbLhMFxOipihf6nrWC4syIm/SwEeec0mNSafiiNnMJwbza/Is6Lw==} + '@emnapi/runtime@1.10.0': + resolution: {integrity: sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==} '@emnapi/wasi-threads@1.2.1': resolution: {integrity: sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==} @@ -653,8 +653,8 @@ packages: resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} engines: {node: '>= 8'} - '@oxc-project/types@0.126.0': - resolution: {integrity: sha512-oGfVtjAgwQVVpfBrbtk4e1XDyWHRFta6BS3GWVzrF8xYBT2VGQAk39yJS/wFSMrZqoiCU4oghT3Ch0HaHGIHcQ==} + '@oxc-project/types@0.127.0': + resolution: {integrity: sha512-aIYXQBo4lCbO4z0R3FHeucQHpF46l2LbMdxRvqvuRuW2OxdnSkcng5B8+K12spgLDj93rtN3+J2Vac/TIO+ciQ==} '@paralleldrive/cuid2@2.3.1': resolution: {integrity: sha512-XO7cAxhnTZl0Yggq6jOgjiOHhbgcO4NqFqwSmQpjK3b6TEE6Uj/jfSk6wzYyemh3+I0sHirKSetjQwn5cZktFw==} @@ -690,103 +690,103 @@ packages: preact: ^10.4.0 || ^11.0.0-0 vite: '>=2.0.0' - '@rolldown/binding-android-arm64@1.0.0-rc.16': - resolution: {integrity: sha512-rhY3k7Bsae9qQfOtph2Pm2jZEA+s8Gmjoz4hhmx70K9iMQ/ddeae+xhRQcM5IuVx5ry1+bGfkvMn7D6MJggVSA==} + '@rolldown/binding-android-arm64@1.0.0-rc.17': + resolution: {integrity: sha512-s70pVGhw4zqGeFnXWvAzJDlvxhlRollagdCCKRgOsgUOH3N1l0LIxf83AtGzmb5SiVM4Hjl5HyarMRfdfj3DaQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [android] - '@rolldown/binding-darwin-arm64@1.0.0-rc.16': - resolution: {integrity: sha512-rNz0yK078yrNn3DrdgN+PKiMOW8HfQ92jQiXxwX8yW899ayV00MLVdaCNeVBhG/TbH3ouYVObo8/yrkiectkcQ==} + '@rolldown/binding-darwin-arm64@1.0.0-rc.17': + resolution: {integrity: sha512-4ksWc9n0mhlZpZ9PMZgTGjeOPRu8MB1Z3Tz0Mo02eWfWCHMW1zN82Qz/pL/rC+yQa+8ZnutMF0JjJe7PjwasYw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [darwin] - '@rolldown/binding-darwin-x64@1.0.0-rc.16': - resolution: {integrity: sha512-r/OmdR00HmD4i79Z//xO06uEPOq5hRXdhw7nzkxQxwSavs3PSHa1ijntdpOiZ2mzOQ3fVVu8C1M19FoNM+dMUQ==} + '@rolldown/binding-darwin-x64@1.0.0-rc.17': + resolution: {integrity: sha512-SUSDOI6WwUVNcWxd02QEBjLdY1VPHvlEkw6T/8nYG322iYWCTxRb1vzk4E+mWWYehTp7ERibq54LSJGjmouOsw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [darwin] - '@rolldown/binding-freebsd-x64@1.0.0-rc.16': - resolution: {integrity: sha512-KcRE5w8h0OnjUatG8pldyD14/CQ5Phs1oxfR+3pKDjboHRo9+MkqQaiIZlZRpsxC15paeXme/I127tUa9TXJ6g==} + '@rolldown/binding-freebsd-x64@1.0.0-rc.17': + resolution: {integrity: sha512-hwnz3nw9dbJ05EDO/PvcjaaewqqDy7Y1rn1UO81l8iIK1GjenME75dl16ajbvSSMfv66WXSRCYKIqfgq2KCfxw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [freebsd] - '@rolldown/binding-linux-arm-gnueabihf@1.0.0-rc.16': - resolution: {integrity: sha512-bT0guA1bpxEJ/ZhTRniQf7rNF8ybvXOuWbNIeLABaV5NGjx4EtOWBTSRGWFU9ZWVkPOZ+HNFP8RMcBokBiZ0Kg==} + '@rolldown/binding-linux-arm-gnueabihf@1.0.0-rc.17': + resolution: {integrity: sha512-IS+W7epTcwANmFSQFrS1SivEXHtl1JtuQA9wlxrZTcNi6mx+FDOYrakGevvvTwgj2JvWiK8B29/qD9BELZPyXQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm] os: [linux] - '@rolldown/binding-linux-arm64-gnu@1.0.0-rc.16': - resolution: {integrity: sha512-+tHktCHWV8BDQSjemUqm/Jl/TPk3QObCTIjmdDy/nlupcujZghmKK2962LYrqFpWu+ai01AN/REOH3NEpqvYQg==} + '@rolldown/binding-linux-arm64-gnu@1.0.0-rc.17': + resolution: {integrity: sha512-e6usGaHKW5BMNZOymS1UcEYGowQMWcgZ71Z17Sl/h2+ZziNJ1a9n3Zvcz6LdRyIW5572wBCTH/Z+bKuZouGk9Q==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] libc: [glibc] - '@rolldown/binding-linux-arm64-musl@1.0.0-rc.16': - resolution: {integrity: sha512-3fPzdREH806oRLxpTWW1Gt4tQHs0TitZFOECB2xzCFLPKnSOy90gwA7P29cksYilFO6XVRY1kzga0cL2nRjKPg==} + '@rolldown/binding-linux-arm64-musl@1.0.0-rc.17': + resolution: {integrity: sha512-b/CgbwAJpmrRLp02RPfhbudf5tZnN9nsPWK82znefso832etkem8H7FSZwxrOI9djcdTP7U6YfNhbRnh7djErg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] libc: [musl] - '@rolldown/binding-linux-ppc64-gnu@1.0.0-rc.16': - resolution: {integrity: sha512-EKwI1tSrLs7YVw+JPJT/G2dJQ1jl9qlTTTEG0V2Ok/RdOenRfBw2PQdLPyjhIu58ocdBfP7vIRN/pvMsPxs/AQ==} + '@rolldown/binding-linux-ppc64-gnu@1.0.0-rc.17': + resolution: {integrity: sha512-4EII1iNGRUN5WwGbF/kOh/EIkoDN9HsupgLQoXfY+D1oyJm7/F4t5PYU5n8SWZgG0FEwakyM8pGgwcBYruGTlA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [ppc64] os: [linux] libc: [glibc] - '@rolldown/binding-linux-s390x-gnu@1.0.0-rc.16': - resolution: {integrity: sha512-Uknladnb3Sxqu6SEcqBldQyJUpk8NleooZEc0MbRBJ4inEhRYWZX0NJu12vNf2mqAq7gsofAxHrGghiUYjhaLQ==} + '@rolldown/binding-linux-s390x-gnu@1.0.0-rc.17': + resolution: {integrity: sha512-AH8oq3XqQo4IibpVXvPeLDI5pzkpYn0WiZAfT05kFzoJ6tQNzwRdDYQ45M8I/gslbodRZwW8uxLhbSBbkv96rA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [s390x] os: [linux] libc: [glibc] - '@rolldown/binding-linux-x64-gnu@1.0.0-rc.16': - resolution: {integrity: sha512-FIb8+uG49sZBtLTn+zt1AJ20TqVcqWeSIyoVt0or7uAWesgKaHbiBh6OpA/k9v0LTt+PTrb1Lao133kP4uVxkg==} + '@rolldown/binding-linux-x64-gnu@1.0.0-rc.17': + resolution: {integrity: sha512-cLnjV3xfo7KslbU41Z7z8BH/E1y5mzUYzAqih1d1MDaIGZRCMqTijqLv76/P7fyHuvUcfGsIpqCdddbxLLK9rA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] libc: [glibc] - '@rolldown/binding-linux-x64-musl@1.0.0-rc.16': - resolution: {integrity: sha512-RuERhF9/EgWxZEXYWCOaViUWHIboceK4/ivdtQ3R0T44NjLkIIlGIAVAuCddFxsZ7vnRHtNQUrt2vR2n2slB2w==} + '@rolldown/binding-linux-x64-musl@1.0.0-rc.17': + resolution: {integrity: sha512-0phclDw1spsL7dUB37sIARuis2tAgomCJXAHZlpt8PXZ4Ba0dRP1e+66lsRqrfhISeN9bEGNjQs+T/Fbd7oYGw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] libc: [musl] - '@rolldown/binding-openharmony-arm64@1.0.0-rc.16': - resolution: {integrity: sha512-mXcXnvd9GpazCxeUCCnZ2+YF7nut+ZOEbE4GtaiPtyY6AkhZWbK70y1KK3j+RDhjVq5+U8FySkKRb/+w0EeUwA==} + '@rolldown/binding-openharmony-arm64@1.0.0-rc.17': + resolution: {integrity: sha512-0ag/hEgXOwgw4t8QyQvUCxvEg+V0KBcA6YuOx9g0r02MprutRF5dyljgm3EmR02O292UX7UeS6HzWHAl6KgyhA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [openharmony] - '@rolldown/binding-wasm32-wasi@1.0.0-rc.16': - resolution: {integrity: sha512-3Q2KQxnC8IJOLqXmUMoYwyIPZU9hzRbnHaoV3Euz+VVnjZKcY8ktnNP8T9R4/GGQtb27C/UYKABxesKWb8lsvQ==} + '@rolldown/binding-wasm32-wasi@1.0.0-rc.17': + resolution: {integrity: sha512-LEXei6vo0E5wTGwpkJ4KoT3OZJRnglwldt5ziLzOlc6qqb55z4tWNq2A+PFqCJuvWWdP53CVhG1Z9NtToDPJrA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [wasm32] - '@rolldown/binding-win32-arm64-msvc@1.0.0-rc.16': - resolution: {integrity: sha512-tj7XRemQcOcFwv7qhpUxMTBbI5mWMlE4c1Omhg5+h8GuLXzyj8HviYgR+bB2DMDgRqUE+jiDleqSCRjx4aYk/Q==} + '@rolldown/binding-win32-arm64-msvc@1.0.0-rc.17': + resolution: {integrity: sha512-gUmyzBl3SPMa6hrqFUth9sVfcLBlYsbMzBx5PlexMroZStgzGqlZ26pYG89rBb45Mnia+oil6YAIFeEWGWhoZA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [win32] - '@rolldown/binding-win32-x64-msvc@1.0.0-rc.16': - resolution: {integrity: sha512-PH5DRZT+F4f2PTXRXR8uJxnBq2po/xFtddyabTJVJs/ZYVHqXPEgNIr35IHTEa6bpa0Q8Awg+ymkTaGnKITw4g==} + '@rolldown/binding-win32-x64-msvc@1.0.0-rc.17': + resolution: {integrity: sha512-3hkiolcUAvPB9FLb3UZdfjVVNWherN1f/skkGWJP/fgSQhYUZpSIRr0/I8ZK9TkF3F7kxvJAk0+IcKvPHk9qQg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [win32] - '@rolldown/pluginutils@1.0.0-rc.16': - resolution: {integrity: sha512-45+YtqxLYKDWQouLKCrpIZhke+nXxhsw+qAHVzHDVwttyBlHNBVs2K25rDXrZzhpTp9w1FlAlvweV1H++fdZoA==} + '@rolldown/pluginutils@1.0.0-rc.17': + resolution: {integrity: sha512-n8iosDOt6Ig1UhJ2AYqoIhHWh/isz0xpicHTzpKBeotdVsTEcxsSA/i3EVM7gQAj0rU27OLAxCjzlj15IWY7bg==} '@rollup/pluginutils@4.2.1': resolution: {integrity: sha512-iKnFXr7NkdZAIHiIWE+BX5ULi/ucVFYWD6TbAV+rZctiRTY2PL6tsIKhoIOaoskiWAkgu+VsbXgUVDNLHf+InQ==} @@ -1109,8 +1109,8 @@ packages: engines: {node: '>=0.4.0'} hasBin: true - ajv@6.14.0: - resolution: {integrity: sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==} + ajv@6.15.0: + resolution: {integrity: sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==} alova@3.5.1: resolution: {integrity: sha512-avrWPyFFWW51YLoy0S3OleNw1BV0GqNI+DSdWHfFbAoKZp80cXCCc7OtjA6OWeyhCOMglUMwo9O8j5huwnzFtQ==} @@ -1188,8 +1188,8 @@ packages: base64-js@1.5.1: resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} - baseline-browser-mapping@2.10.20: - resolution: {integrity: sha512-1AaXxEPfXT+GvTBJFuy4yXVHWJBXa4OdbIebGN/wX5DlsIkU0+wzGnd2lOzokSk51d5LUmqjgBLRLlypLUqInQ==} + baseline-browser-mapping@2.10.22: + resolution: {integrity: sha512-6qruVrb5rse6WylFkU0FhBKKGuecWseqdpQfhkawn6ztyk2QlfwSRjsDxMCLJrkfmfN21qvhl9ABgaMeRkuwww==} engines: {node: '>=6.0.0'} hasBin: true @@ -1523,8 +1523,8 @@ packages: duplexer3@0.1.5: resolution: {integrity: sha512-1A8za6ws41LQgv9HrE/66jyC5yuSjQ3L/KOpFtoBilsAK2iA2wuS5rTt1OCzIvtS2V7nVmedsUU+DGRcjBmOYA==} - electron-to-chromium@1.5.343: - resolution: {integrity: sha512-YHnQ3MXI08icvL9ZKnEBy05F2EQ8ob01UaMOuMbM8l+4UcAq6MPPbBTJBbsBUg3H8JeZNt+O4fjsoWth3p6IFg==} + electron-to-chromium@1.5.344: + resolution: {integrity: sha512-4MxfbmNDm+KPh066EZy+eUnkcDPcZ35wNmOWzFuh/ijvHsve6kbLTLURy88uCNK5FbpN+yk2nQY6BYh1GEt+wg==} emoji-regex@10.6.0: resolution: {integrity: sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==} @@ -2881,8 +2881,8 @@ packages: deprecated: Rimraf versions prior to v4 are no longer supported hasBin: true - rolldown@1.0.0-rc.16: - resolution: {integrity: sha512-rzi5WqKzEZw3SooTt7cgm4eqIoujPIyGcJNGFL7iPEuajQw7vxMHUkXylu4/vhCkJGXsgRmxqMKXUpT6FEgl0g==} + rolldown@1.0.0-rc.17: + resolution: {integrity: sha512-ZrT53oAKrtA4+YtBWPQbtPOxIbVDbxT0orcYERKd63VJTF13zPcgXTvD4843L8pcsI7M6MErt8QtON6lrB9tyA==} engines: {node: ^20.19.0 || >=22.12.0} hasBin: true @@ -3134,8 +3134,8 @@ packages: resolution: {integrity: sha512-ZOn6nJUgvgC09+doCEF3oB+r3ag7kUvlsXEGX069QRD60p+P3uP7XG9N2/at+EyIRGSN//ZY3LyEotA1YpmjuA==} engines: {node: '>=4'} - terser@5.46.1: - resolution: {integrity: sha512-vzCjQO/rgUuK9sf8VJZvjqiqiHFaZLnOiimmUuOKODxWL8mm/xua7viT7aqX7dgPY60otQjUotzFMmCB4VdmqQ==} + terser@5.46.2: + resolution: {integrity: sha512-uxfo9fPcSgLDYob/w1FuL0c99MWiJDnv+5qXSQc5+Ki5NjVNsYi66INnMFBjf6uFz6OnX12piJQPF4IpjJTNTw==} engines: {node: '>=10'} hasBin: true @@ -3279,8 +3279,8 @@ packages: peerDependencies: vite: '*' - vite@8.0.9: - resolution: {integrity: sha512-t7g7GVRpMXjNpa67HaVWI/8BWtdVIQPCL2WoozXXA7LBGEFK4AkkKkHx2hAQf5x1GZSlcmEDPkVLSGahxnEEZw==} + vite@8.0.10: + resolution: {integrity: sha512-rZuUu9j6J5uotLDs+cAA4O5H4K1SfPliUlQwqa6YEwSrWDZzP4rhm00oJR5snMewjxF5V/K3D4kctsUTsIU9Mw==} engines: {node: ^20.19.0 || >=22.12.0} hasBin: true peerDependencies: @@ -3538,13 +3538,13 @@ snapshots: '@babel/helper-string-parser': 7.27.1 '@babel/helper-validator-identifier': 7.28.5 - '@emnapi/core@1.9.2': + '@emnapi/core@1.10.0': dependencies: '@emnapi/wasi-threads': 1.2.1 tslib: 2.8.1 optional: true - '@emnapi/runtime@1.9.2': + '@emnapi/runtime@1.10.0': dependencies: tslib: 2.8.1 optional: true @@ -3879,10 +3879,10 @@ snapshots: optionalDependencies: '@types/react': 19.2.14 - '@napi-rs/wasm-runtime@1.1.4(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2)': + '@napi-rs/wasm-runtime@1.1.4(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)': dependencies: - '@emnapi/core': 1.9.2 - '@emnapi/runtime': 1.9.2 + '@emnapi/core': 1.10.0 + '@emnapi/runtime': 1.10.0 '@tybys/wasm-util': 0.10.1 optional: true @@ -3900,7 +3900,7 @@ snapshots: '@nodelib/fs.scandir': 2.1.5 fastq: 1.20.1 - '@oxc-project/types@0.126.0': {} + '@oxc-project/types@0.127.0': {} '@paralleldrive/cuid2@2.3.1': dependencies: @@ -3912,19 +3912,19 @@ snapshots: dependencies: preact: 10.29.1 - '@preact/preset-vite@2.10.5(@babel/core@7.29.0)(preact@10.29.1)(rollup@4.59.0)(vite@8.0.9(@types/node@25.6.0)(esbuild@0.27.4)(terser@5.46.1))': + '@preact/preset-vite@2.10.5(@babel/core@7.29.0)(preact@10.29.1)(rollup@4.59.0)(vite@8.0.10(@types/node@25.6.0)(esbuild@0.27.4)(terser@5.46.2))': dependencies: '@babel/core': 7.29.0 '@babel/plugin-transform-react-jsx': 7.28.6(@babel/core@7.29.0) '@babel/plugin-transform-react-jsx-development': 7.27.1(@babel/core@7.29.0) - '@prefresh/vite': 2.4.12(preact@10.29.1)(vite@8.0.9(@types/node@25.6.0)(esbuild@0.27.4)(terser@5.46.1)) + '@prefresh/vite': 2.4.12(preact@10.29.1)(vite@8.0.10(@types/node@25.6.0)(esbuild@0.27.4)(terser@5.46.2)) '@rollup/pluginutils': 5.3.0(rollup@4.59.0) babel-plugin-transform-hook-names: 1.0.2(@babel/core@7.29.0) debug: 4.4.3 magic-string: 0.30.21 picocolors: 1.1.1 - vite: 8.0.9(@types/node@25.6.0)(esbuild@0.27.4)(terser@5.46.1) - vite-prerender-plugin: 0.5.13(vite@8.0.9(@types/node@25.6.0)(esbuild@0.27.4)(terser@5.46.1)) + vite: 8.0.10(@types/node@25.6.0)(esbuild@0.27.4)(terser@5.46.2) + vite-prerender-plugin: 0.5.13(vite@8.0.10(@types/node@25.6.0)(esbuild@0.27.4)(terser@5.46.2)) zimmerframe: 1.1.4 transitivePeerDependencies: - preact @@ -3939,7 +3939,7 @@ snapshots: '@prefresh/utils@1.2.1': {} - '@prefresh/vite@2.4.12(preact@10.29.1)(vite@8.0.9(@types/node@25.6.0)(esbuild@0.27.4)(terser@5.46.1))': + '@prefresh/vite@2.4.12(preact@10.29.1)(vite@8.0.10(@types/node@25.6.0)(esbuild@0.27.4)(terser@5.46.2))': dependencies: '@babel/core': 7.29.0 '@prefresh/babel-plugin': 0.5.3 @@ -3947,60 +3947,60 @@ snapshots: '@prefresh/utils': 1.2.1 '@rollup/pluginutils': 4.2.1 preact: 10.29.1 - vite: 8.0.9(@types/node@25.6.0)(esbuild@0.27.4)(terser@5.46.1) + vite: 8.0.10(@types/node@25.6.0)(esbuild@0.27.4)(terser@5.46.2) transitivePeerDependencies: - supports-color - '@rolldown/binding-android-arm64@1.0.0-rc.16': + '@rolldown/binding-android-arm64@1.0.0-rc.17': optional: true - '@rolldown/binding-darwin-arm64@1.0.0-rc.16': + '@rolldown/binding-darwin-arm64@1.0.0-rc.17': optional: true - '@rolldown/binding-darwin-x64@1.0.0-rc.16': + '@rolldown/binding-darwin-x64@1.0.0-rc.17': optional: true - '@rolldown/binding-freebsd-x64@1.0.0-rc.16': + '@rolldown/binding-freebsd-x64@1.0.0-rc.17': optional: true - '@rolldown/binding-linux-arm-gnueabihf@1.0.0-rc.16': + '@rolldown/binding-linux-arm-gnueabihf@1.0.0-rc.17': optional: true - '@rolldown/binding-linux-arm64-gnu@1.0.0-rc.16': + '@rolldown/binding-linux-arm64-gnu@1.0.0-rc.17': optional: true - '@rolldown/binding-linux-arm64-musl@1.0.0-rc.16': + '@rolldown/binding-linux-arm64-musl@1.0.0-rc.17': optional: true - '@rolldown/binding-linux-ppc64-gnu@1.0.0-rc.16': + '@rolldown/binding-linux-ppc64-gnu@1.0.0-rc.17': optional: true - '@rolldown/binding-linux-s390x-gnu@1.0.0-rc.16': + '@rolldown/binding-linux-s390x-gnu@1.0.0-rc.17': optional: true - '@rolldown/binding-linux-x64-gnu@1.0.0-rc.16': + '@rolldown/binding-linux-x64-gnu@1.0.0-rc.17': optional: true - '@rolldown/binding-linux-x64-musl@1.0.0-rc.16': + '@rolldown/binding-linux-x64-musl@1.0.0-rc.17': optional: true - '@rolldown/binding-openharmony-arm64@1.0.0-rc.16': + '@rolldown/binding-openharmony-arm64@1.0.0-rc.17': optional: true - '@rolldown/binding-wasm32-wasi@1.0.0-rc.16': + '@rolldown/binding-wasm32-wasi@1.0.0-rc.17': dependencies: - '@emnapi/core': 1.9.2 - '@emnapi/runtime': 1.9.2 - '@napi-rs/wasm-runtime': 1.1.4(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2) + '@emnapi/core': 1.10.0 + '@emnapi/runtime': 1.10.0 + '@napi-rs/wasm-runtime': 1.1.4(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0) optional: true - '@rolldown/binding-win32-arm64-msvc@1.0.0-rc.16': + '@rolldown/binding-win32-arm64-msvc@1.0.0-rc.17': optional: true - '@rolldown/binding-win32-x64-msvc@1.0.0-rc.16': + '@rolldown/binding-win32-x64-msvc@1.0.0-rc.17': optional: true - '@rolldown/pluginutils@1.0.0-rc.16': {} + '@rolldown/pluginutils@1.0.0-rc.17': {} '@rollup/pluginutils@4.2.1': dependencies: @@ -4293,7 +4293,7 @@ snapshots: acorn@8.16.0: {} - ajv@6.14.0: + ajv@6.15.0: dependencies: fast-deep-equal: 3.1.3 fast-json-stable-stringify: 2.1.0 @@ -4355,7 +4355,7 @@ snapshots: base64-js@1.5.1: {} - baseline-browser-mapping@2.10.20: {} + baseline-browser-mapping@2.10.22: {} bin-build@3.0.0: dependencies: @@ -4416,9 +4416,9 @@ snapshots: browserslist@4.28.2: dependencies: - baseline-browser-mapping: 2.10.20 + baseline-browser-mapping: 2.10.22 caniuse-lite: 1.0.30001790 - electron-to-chromium: 1.5.343 + electron-to-chromium: 1.5.344 node-releases: 2.0.38 update-browserslist-db: 1.2.3(browserslist@4.28.2) @@ -4782,7 +4782,7 @@ snapshots: duplexer3@0.1.5: {} - electron-to-chromium@1.5.343: {} + electron-to-chromium@1.5.344: {} emoji-regex@10.6.0: {} @@ -4955,7 +4955,7 @@ snapshots: '@humanwhocodes/module-importer': 1.0.1 '@humanwhocodes/retry': 0.4.3 '@types/estree': 1.0.8 - ajv: 6.14.0 + ajv: 6.15.0 cross-spawn: 7.0.6 debug: 4.4.3 escape-string-regexp: 4.0.0 @@ -6090,35 +6090,35 @@ snapshots: dependencies: glob: 7.2.3 - rolldown@1.0.0-rc.16: + rolldown@1.0.0-rc.17: dependencies: - '@oxc-project/types': 0.126.0 - '@rolldown/pluginutils': 1.0.0-rc.16 + '@oxc-project/types': 0.127.0 + '@rolldown/pluginutils': 1.0.0-rc.17 optionalDependencies: - '@rolldown/binding-android-arm64': 1.0.0-rc.16 - '@rolldown/binding-darwin-arm64': 1.0.0-rc.16 - '@rolldown/binding-darwin-x64': 1.0.0-rc.16 - '@rolldown/binding-freebsd-x64': 1.0.0-rc.16 - '@rolldown/binding-linux-arm-gnueabihf': 1.0.0-rc.16 - '@rolldown/binding-linux-arm64-gnu': 1.0.0-rc.16 - '@rolldown/binding-linux-arm64-musl': 1.0.0-rc.16 - '@rolldown/binding-linux-ppc64-gnu': 1.0.0-rc.16 - '@rolldown/binding-linux-s390x-gnu': 1.0.0-rc.16 - '@rolldown/binding-linux-x64-gnu': 1.0.0-rc.16 - '@rolldown/binding-linux-x64-musl': 1.0.0-rc.16 - '@rolldown/binding-openharmony-arm64': 1.0.0-rc.16 - '@rolldown/binding-wasm32-wasi': 1.0.0-rc.16 - '@rolldown/binding-win32-arm64-msvc': 1.0.0-rc.16 - '@rolldown/binding-win32-x64-msvc': 1.0.0-rc.16 + '@rolldown/binding-android-arm64': 1.0.0-rc.17 + '@rolldown/binding-darwin-arm64': 1.0.0-rc.17 + '@rolldown/binding-darwin-x64': 1.0.0-rc.17 + '@rolldown/binding-freebsd-x64': 1.0.0-rc.17 + '@rolldown/binding-linux-arm-gnueabihf': 1.0.0-rc.17 + '@rolldown/binding-linux-arm64-gnu': 1.0.0-rc.17 + '@rolldown/binding-linux-arm64-musl': 1.0.0-rc.17 + '@rolldown/binding-linux-ppc64-gnu': 1.0.0-rc.17 + '@rolldown/binding-linux-s390x-gnu': 1.0.0-rc.17 + '@rolldown/binding-linux-x64-gnu': 1.0.0-rc.17 + '@rolldown/binding-linux-x64-musl': 1.0.0-rc.17 + '@rolldown/binding-openharmony-arm64': 1.0.0-rc.17 + '@rolldown/binding-wasm32-wasi': 1.0.0-rc.17 + '@rolldown/binding-win32-arm64-msvc': 1.0.0-rc.17 + '@rolldown/binding-win32-x64-msvc': 1.0.0-rc.17 - rollup-plugin-visualizer@7.0.1(rolldown@1.0.0-rc.16)(rollup@4.59.0): + rollup-plugin-visualizer@7.0.1(rolldown@1.0.0-rc.17)(rollup@4.59.0): dependencies: open: 11.0.0 picomatch: 4.0.4 source-map: 0.7.6 yargs: 18.0.0 optionalDependencies: - rolldown: 1.0.0-rc.16 + rolldown: 1.0.0-rc.17 rollup: 4.59.0 rollup@4.59.0: @@ -6362,7 +6362,7 @@ snapshots: temp-dir: 1.0.0 uuid: 3.4.0 - terser@5.46.1: + terser@5.46.2: dependencies: '@jridgewell/source-map': 0.3.11 acorn: 8.16.0 @@ -6477,7 +6477,7 @@ snapshots: spdx-correct: 3.2.0 spdx-expression-parse: 3.0.1 - vite-plugin-imagemin@0.6.1(vite@8.0.9(@types/node@25.6.0)(esbuild@0.27.4)(terser@5.46.1)): + vite-plugin-imagemin@0.6.1(vite@8.0.10(@types/node@25.6.0)(esbuild@0.27.4)(terser@5.46.2)): dependencies: '@types/imagemin': 7.0.1 '@types/imagemin-gifsicle': 7.0.4 @@ -6502,11 +6502,11 @@ snapshots: imagemin-webp: 6.1.0 jpegtran-bin: 6.0.1 pathe: 0.2.0 - vite: 8.0.9(@types/node@25.6.0)(esbuild@0.27.4)(terser@5.46.1) + vite: 8.0.10(@types/node@25.6.0)(esbuild@0.27.4)(terser@5.46.2) transitivePeerDependencies: - supports-color - vite-prerender-plugin@0.5.13(vite@8.0.9(@types/node@25.6.0)(esbuild@0.27.4)(terser@5.46.1)): + vite-prerender-plugin@0.5.13(vite@8.0.10(@types/node@25.6.0)(esbuild@0.27.4)(terser@5.46.2)): dependencies: kolorist: 1.8.0 magic-string: 0.30.21 @@ -6514,30 +6514,30 @@ snapshots: simple-code-frame: 1.3.0 source-map: 0.7.6 stack-trace: 1.0.0-pre2 - vite: 8.0.9(@types/node@25.6.0)(esbuild@0.27.4)(terser@5.46.1) + vite: 8.0.10(@types/node@25.6.0)(esbuild@0.27.4)(terser@5.46.2) - vite-tsconfig-paths@6.1.1(typescript@6.0.3)(vite@8.0.9(@types/node@25.6.0)(esbuild@0.27.4)(terser@5.46.1)): + vite-tsconfig-paths@6.1.1(typescript@6.0.3)(vite@8.0.10(@types/node@25.6.0)(esbuild@0.27.4)(terser@5.46.2)): dependencies: debug: 4.4.3 globrex: 0.1.2 tsconfck: 3.1.6(typescript@6.0.3) - vite: 8.0.9(@types/node@25.6.0)(esbuild@0.27.4)(terser@5.46.1) + vite: 8.0.10(@types/node@25.6.0)(esbuild@0.27.4)(terser@5.46.2) transitivePeerDependencies: - supports-color - typescript - vite@8.0.9(@types/node@25.6.0)(esbuild@0.27.4)(terser@5.46.1): + vite@8.0.10(@types/node@25.6.0)(esbuild@0.27.4)(terser@5.46.2): dependencies: lightningcss: 1.32.0 picomatch: 4.0.4 postcss: 8.5.10 - rolldown: 1.0.0-rc.16 + rolldown: 1.0.0-rc.17 tinyglobby: 0.2.16 optionalDependencies: '@types/node': 25.6.0 esbuild: 0.27.4 fsevents: 2.3.3 - terser: 5.46.1 + terser: 5.46.2 which-typed-array@1.1.20: dependencies: diff --git a/interface/src/api/endpoints.ts b/interface/src/api/endpoints.ts index 95d462689..e598d7aad 100644 --- a/interface/src/api/endpoints.ts +++ b/interface/src/api/endpoints.ts @@ -58,11 +58,5 @@ export const alovaInstance = createAlova({ } }); -export const alovaInstanceGH = createAlova({ - baseURL: - process.env.NODE_ENV === 'development' - ? '/gh' - : 'https://api.github.com/repos/emsesp/EMS-ESP32/releases', - statesHook: ReactHook, - requestAdapter: xhrRequestAdapter() -}); +export const DOCS_BASE_URL = + process.env.NODE_ENV === 'development' ? '/emsesp.org' : 'https://emsesp.org'; diff --git a/interface/src/api/system.ts b/interface/src/api/system.ts index 1b9d1a37a..6910c2df2 100644 --- a/interface/src/api/system.ts +++ b/interface/src/api/system.ts @@ -1,6 +1,6 @@ import type { LogSettings, SystemStatus } from 'types'; -import { alovaInstance, alovaInstanceGH } from './endpoints'; +import { DOCS_BASE_URL, alovaInstance } from './endpoints'; // systemStatus - also used to ping in System Monitor for pinging export const readSystemStatus = () => @@ -13,28 +13,13 @@ export const updateLogSettings = (data: LogSettings) => alovaInstance.Post('/rest/logSettings', data); export const fetchLogES = () => alovaInstance.Get('/es/log'); -// Get versions from GitHub -// cache for 10 minutes to stop getting the IP blocked by GitHub -export const getStableVersion = () => - alovaInstanceGH.Get('latest', { - cacheFor: 60 * 10 * 1000, - transform(response: { data: { name: string; published_at: string } }) { - return { - name: response.data.name.substring(1), - published_at: response.data.published_at - }; - } - }); -export const getDevVersion = () => - alovaInstanceGH.Get('tags/latest', { - cacheFor: 60 * 10 * 1000, - transform(response: { data: { name: string; published_at: string } }) { - return { - name: response.data.name.split(/\s+/).splice(-1)[0]?.substring(1) || '', - published_at: response.data.published_at - }; - } - }); +// get versions from emsesp.org/versions.json +// uses native fetch (no custom headers) to keep this as a "simple" CORS +export const getVersions = async (): Promise => { + const res = await fetch(`${DOCS_BASE_URL}/versions.json`); + if (!res.ok) throw new Error(res.statusText); + return res.json() as Promise; +}; const UPLOAD_TIMEOUT = 60000; // 1 minute diff --git a/interface/src/app/status/Version.tsx b/interface/src/app/status/Version.tsx index a3326856b..64a5b1cd6 100644 --- a/interface/src/app/status/Version.tsx +++ b/interface/src/app/status/Version.tsx @@ -34,7 +34,7 @@ import { import * as SystemApi from 'api/system'; import { API, callAction } from 'api/app'; -import { getDevVersion, getStableVersion } from 'api/system'; +import { getVersions } from 'api/system'; import { dialogStyle } from 'CustomTheme'; import { useRequest } from 'alova/client'; @@ -86,8 +86,14 @@ interface UpgradeCheckData { } interface VersionInfo { - name: string; - published_at?: string; + version: string; + date: string; +} + +interface Versions { + stable: VersionInfo; + dev: VersionInfo; + last_updated: string; } // Memoized components for better performance @@ -156,8 +162,8 @@ const VersionInfoDialog = memo( {isPartition ? typeof version === 'string' ? version - : version?.name - : version?.name} + : version?.version + : version?.version} @@ -224,7 +230,7 @@ const VersionInfoDialog = memo( )} - {version?.published_at && ( + {version.date && ( - {prettyDateTime(locale, new Date(version.published_at))} + {prettyDateTime(locale, new Date(version.date))} )} @@ -296,11 +302,11 @@ const InstallDialog = memo( if (!latestVersion || !latestDevVersion) return ''; const version = fetchDevVersion ? latestDevVersion : latestVersion; - const filename = `EMS-ESP-${version.name.replaceAll('.', '_')}-${platform}.bin`; + const filename = `EMS-ESP-${version.version.replaceAll('.', '_')}-${platform}.bin`; return fetchDevVersion ? `${DEV_URL}${filename}` - : `${STABLE_URL}v${version.name}/${filename}`; + : `${STABLE_URL}v${version.version}/${filename}`; }, [fetchDevVersion, latestVersion, latestDevVersion, platform]); return ( @@ -312,7 +318,7 @@ const InstallDialog = memo( {LL.INSTALL_VERSION( downloadOnly ? LL.DOWNLOAD(1) : LL.INSTALL(), - fetchDevVersion ? latestDevVersion?.name : latestVersion?.name + fetchDevVersion ? latestDevVersion?.version : latestVersion?.version )} {upgradeImportantMessageType === 2 && LL.UPGRADE_IMPORTANT_MESSAGES_2()} @@ -436,7 +442,9 @@ const Version = () => { const { LL, locale } = useI18nContext(); const { me } = useContext(AuthenticatedContext); - // State management + const [latestVersion, setLatestVersion] = useState(); + const [latestDevVersion, setLatestDevVersion] = useState(); + const [restarting, setRestarting] = useState(false); const [openInstallDialog, setOpenInstallDialog] = useState(false); @@ -490,8 +498,32 @@ const Version = () => { { immediate: false } ); - const { data: latestVersion } = useRequest(getStableVersion); - const { data: latestDevVersion } = useRequest(getDevVersion); + // Fetch versions.json from emsesp.org once on mount. + // Uses plain fetch (not alova) so the request stays a "simple" CORS request and avoids a preflight that emsesp.org rejects. + // sendCheckUpgrade is stored in a ref because alova's useRequest returns a new function reference each render + const sendCheckUpgradeRef = useRef(sendCheckUpgrade); + sendCheckUpgradeRef.current = sendCheckUpgrade; + useEffect(() => { + let cancelled = false; + getVersions() + .then((versions) => { + if (cancelled) return; + setLatestVersion(versions.stable); + setLatestDevVersion(versions.dev); + sendCheckUpgradeRef.current( + `${versions.stable.version},${versions.dev.version}` + ); + setInternetLive(true); + }) + .catch((error: unknown) => { + if (cancelled) return; + toast.error(error instanceof Error ? error.message : 'An error occurred'); + setInternetLive(false); + }); + return () => { + cancelled = true; + }; + }, []); const { send: sendAPI } = useRequest((data: APIcall) => API(data), { immediate: false @@ -517,10 +549,8 @@ const Version = () => { toast.error(String(error.error?.message || 'An error occurred')); }); - // Memoized values const platform = useMemo(() => (data ? getPlatform(data) : ''), [data]); - // Memoize filtered partitions to avoid recomputing on every render const otherPartitions = useMemo( () => data?.partitions.filter((p) => p.partition !== data.partition) ?? [], [data] @@ -534,8 +564,8 @@ const Version = () => { const partitionData = data?.partitions.find((p) => p.partition === partition); if (partitionData) { setPartitionVersion({ - name: partitionData.version, - published_at: partitionData.install_date ?? '' + version: partitionData.version, + date: partitionData.install_date ?? '' }); setPartition(partitionData.partition); setFirmwareSize(partitionData.size); @@ -576,7 +606,7 @@ const Version = () => { const showPartitionDialog = useCallback( (version: string, partition: string, install_date: string) => { setOpenInstallPartitionDialog(true); - setPartitionVersion({ name: version, published_at: install_date }); + setPartitionVersion({ version: version, date: install_date }); setPartition(partition); }, [] @@ -586,7 +616,7 @@ const Version = () => { (useDevVersion: boolean) => { setFetchDevVersion(useDevVersion); void checkUpgradeImportantMessages( - useDevVersion ? latestDevVersion?.name : latestVersion?.name + useDevVersion ? latestDevVersion?.version : latestVersion?.version ); setOpenInstallDialog(true); }, @@ -607,25 +637,8 @@ const Version = () => { setPartition(''); }, []); - // check upgrades - only once when both versions are available - const upgradeCheckedRef = useRef(false); - useEffect(() => { - if (latestVersion && latestDevVersion && !upgradeCheckedRef.current) { - upgradeCheckedRef.current = true; - const versions = `${latestDevVersion.name},${latestVersion.name}`; - sendCheckUpgrade(versions) - .catch((error: Error) => { - toast.error(`Failed to check for upgrades: ${error.message}`); - }) - .finally(() => { - setInternetLive(true); - }); - } - }, [latestVersion, latestDevVersion, sendCheckUpgrade]); - useLayoutTitle('EMS-ESP Firmware'); - // Memoized button rendering logic const showButtons = useCallback( (showingDev: boolean) => { const choice = showingDev @@ -818,7 +831,7 @@ const Version = () => { - {latestVersion?.name} + {latestVersion?.version} setShowVersionInfo(1)} aria-label={LL.FIRMWARE_VERSION_INFO()} @@ -834,7 +847,7 @@ const Version = () => { - {latestDevVersion?.name} + {latestDevVersion?.version} setShowVersionInfo(2)} aria-label={LL.FIRMWARE_VERSION_INFO()} @@ -880,7 +893,7 @@ const Version = () => { /> { - const data = { - name: 'v' + LATEST_DEV_VERSION, - published_at: new Date().toISOString() // use todays date - }; - console.log('returning latest development version (today): ', data); - return data; - }) - .get(GH_ENDPOINT_ROOT + '/latest', () => { - const data = { - name: 'v' + LATEST_STABLE_VERSION, - published_at: '2025-03-01T13:29:13.999Z' - }; - console.log('returning latest stable version: ', data); - return data; - }); +// Mock emsesp.org/versions.json +router.get(EMSESP_VERSIONS_ENDPOINT, () => { + const data = { + stable: { + version: LATEST_STABLE_VERSION, + date: '2026-04-25' + }, + dev: { + version: LATEST_DEV_VERSION, + date: '2026-04-25' + }, + last_updated: new Date().toISOString() + }; + console.log('sending versions.json: ', data); + return data; +}); // const logger: ResponseHandler = (response, request) => { // console.log( diff --git a/src/core/system.cpp b/src/core/system.cpp index b29e3f4c1..45e5c6777 100644 --- a/src/core/system.cpp +++ b/src/core/system.cpp @@ -2281,7 +2281,6 @@ std::string System::get_metrics_prometheus() { } result += info_metric; - // TODO fix, as local_info_labels is always empty here if (!local_info_labels.empty()) { result += "{"; bool first = true; From 5ecda88457d1af67df259e1551ba927a262b562b Mon Sep 17 00:00:00 2001 From: proddy Date: Sat, 25 Apr 2026 21:14:16 +0200 Subject: [PATCH 05/48] inlclude full date/time --- .github/workflows/dev_release.yml | 2 +- .github/workflows/stable_release.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/dev_release.yml b/.github/workflows/dev_release.yml index 04365a655..3a66fede2 100644 --- a/.github/workflows/dev_release.yml +++ b/.github/workflows/dev_release.yml @@ -80,7 +80,7 @@ jobs: - name: Update version in Cloudflare KV store run: | - JSON_DATA='{"version": "${{steps.build_info.outputs.VERSION}}", "date": "$(date -u +%Y-%m-%d)"}' + JSON_DATA="{\"version\": \"${{steps.build_info.outputs.VERSION}}\", \"date\": \"$(date -u +%Y-%m-%dT%H:%M:%SZ)\"}" curl -X PUT "https://api.cloudflare.com/client/v4/accounts/${{ secrets.CF_ACCOUNT_ID }}/storage/kv/namespaces/${{ secrets.CF_NAMESPACE_ID }}/values/dev" \ -H "Authorization: Bearer ${{ secrets.CF_API_TOKEN }}" \ -H "Content-Type: text/plain" \ diff --git a/.github/workflows/stable_release.yml b/.github/workflows/stable_release.yml index d18cd52b9..84df90706 100644 --- a/.github/workflows/stable_release.yml +++ b/.github/workflows/stable_release.yml @@ -64,7 +64,7 @@ jobs: - name: Update version in Cloudflare KV store run: | - JSON_DATA='{"version": "${{ github.event.release.tag_name }}", "date": "$(date -u +%Y-%m-%d)"}' + JSON_DATA="{\"version\": \"${{ github.event.release.tag_name }}\", \"date\": \"$(date -u +%Y-%m-%dT%H:%M:%SZ)\"}" curl -X PUT "https://api.cloudflare.com/client/v4/accounts/${{ secrets.CF_ACCOUNT_ID }}/storage/kv/namespaces/${{ secrets.CF_NAMESPACE_ID }}/values/stable" \ -H "Authorization: Bearer ${{ secrets.CF_API_TOKEN }}" \ -H "Content-Type: text/plain" \ From ee7be1d907bdeb6818d4643c3cfca615f3fc315c Mon Sep 17 00:00:00 2001 From: proddy Date: Sun, 26 Apr 2026 12:20:48 +0200 Subject: [PATCH 06/48] add --- .github/workflows/update_versions.yml | 31 +++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 .github/workflows/update_versions.yml diff --git a/.github/workflows/update_versions.yml b/.github/workflows/update_versions.yml new file mode 100644 index 000000000..6bceead5e --- /dev/null +++ b/.github/workflows/update_versions.yml @@ -0,0 +1,31 @@ +name: 'Update versions' + +on: + workflow_dispatch: + +permissions: + contents: write + +jobs: + update-version: + name: 'Update versions in Cloudflare KV store' + runs-on: ubuntu-latest + steps: + + - name: Checkout repository + uses: actions/checkout@v6 + + - name: Get and Send EMS-ESP version to Cloudflare + run: | + version=$(grep -E '^#define EMSESP_APP_VERSION' ./src/emsesp_version.h | awk -F'"' '{print $2}') + if [ "$GITHUB_REF" = "refs/heads/main" ]; then + KV_ENV="stable" + else + KV_ENV="dev" + fi + JSON_DATA="{\"version\": \"${version}\", \"date\": \"$(date -u +%Y-%m-%dT%H:%M:%SZ)\"}" + curl -X PUT "https://api.cloudflare.com/client/v4/accounts/${{ secrets.CF_ACCOUNT_ID }}/storage/kv/namespaces/${{ secrets.CF_NAMESPACE_ID }}/values/$KV_ENV" \ + -H "Authorization: Bearer ${{ secrets.CF_API_TOKEN }}" \ + -H "Content-Type: text/plain" \ + -d "$JSON_DATA" + \ No newline at end of file From a9db134d3ad753ce7278e9b64d47fff501a12127 Mon Sep 17 00:00:00 2001 From: proddy Date: Sun, 26 Apr 2026 13:24:40 +0200 Subject: [PATCH 07/48] version updates --- .github/workflows/dev_release.yml | 21 ++++++++++++++----- .github/workflows/stable_release.yml | 30 +++++++++++++++++++++------ .github/workflows/update_versions.yml | 25 +++++++++++++++------- 3 files changed, 57 insertions(+), 19 deletions(-) diff --git a/.github/workflows/dev_release.yml b/.github/workflows/dev_release.yml index 3a66fede2..97e4f5ffc 100644 --- a/.github/workflows/dev_release.yml +++ b/.github/workflows/dev_release.yml @@ -79,9 +79,20 @@ jobs: ./build/firmware/*.* - name: Update version in Cloudflare KV store + env: + CF_ACCOUNT_ID: ${{ secrets.CF_ACCOUNT_ID }} + CF_NAMESPACE_ID: ${{ secrets.CF_NAMESPACE_ID }} + CF_API_TOKEN: ${{ secrets.CF_API_TOKEN }} + VERSION: ${{ steps.build_info.outputs.VERSION }} run: | - JSON_DATA="{\"version\": \"${{steps.build_info.outputs.VERSION}}\", \"date\": \"$(date -u +%Y-%m-%dT%H:%M:%SZ)\"}" - curl -X PUT "https://api.cloudflare.com/client/v4/accounts/${{ secrets.CF_ACCOUNT_ID }}/storage/kv/namespaces/${{ secrets.CF_NAMESPACE_ID }}/values/dev" \ - -H "Authorization: Bearer ${{ secrets.CF_API_TOKEN }}" \ - -H "Content-Type: text/plain" \ - -d "$JSON_DATA" + JSON_DATA=$(jq -n \ + --arg version "$VERSION" \ + --arg date "$(date -u +%Y-%m-%dT%H:%M:%SZ)" \ + '{version: $version, date: $date}') + echo "JSON_DATA: $JSON_DATA" + curl -sS --fail-with-body \ + -X PUT "https://api.cloudflare.com/client/v4/accounts/${CF_ACCOUNT_ID}/storage/kv/namespaces/${CF_NAMESPACE_ID}/values/dev" \ + -H "Authorization: Bearer ${CF_API_TOKEN}" \ + -H "Content-Type: application/json" \ + -d "$JSON_DATA" + echo diff --git a/.github/workflows/stable_release.yml b/.github/workflows/stable_release.yml index 84df90706..a5fe7b7ae 100644 --- a/.github/workflows/stable_release.yml +++ b/.github/workflows/stable_release.yml @@ -27,10 +27,17 @@ jobs: - name: Checkout repository uses: actions/checkout@v6 - + - name: Enable Corepack run: corepack enable pnpm + + - name: Get the EMS-ESP version + id: build_info + run: | + version=`grep -E '^#define EMSESP_APP_VERSION' ./src/emsesp_version.h | awk -F'"' '{print $2}'` + echo "VERSION=$version" >> $GITHUB_OUTPUT + - name: Install PlatformIO run: | python -m pip install --upgrade pip @@ -63,9 +70,20 @@ jobs: ./build/firmware/*.* - name: Update version in Cloudflare KV store + env: + CF_ACCOUNT_ID: ${{ secrets.CF_ACCOUNT_ID }} + CF_NAMESPACE_ID: ${{ secrets.CF_NAMESPACE_ID }} + CF_API_TOKEN: ${{ secrets.CF_API_TOKEN }} + VERSION: ${{ steps.build_info.outputs.VERSION }} run: | - JSON_DATA="{\"version\": \"${{ github.event.release.tag_name }}\", \"date\": \"$(date -u +%Y-%m-%dT%H:%M:%SZ)\"}" - curl -X PUT "https://api.cloudflare.com/client/v4/accounts/${{ secrets.CF_ACCOUNT_ID }}/storage/kv/namespaces/${{ secrets.CF_NAMESPACE_ID }}/values/stable" \ - -H "Authorization: Bearer ${{ secrets.CF_API_TOKEN }}" \ - -H "Content-Type: text/plain" \ - -d "$JSON_DATA" + JSON_DATA=$(jq -n \ + --arg version "$VERSION" \ + --arg date "$(date -u +%Y-%m-%dT%H:%M:%SZ)" \ + '{version: $version, date: $date}') + echo "JSON_DATA: $JSON_DATA" + curl -sS --fail-with-body \ + -X PUT "https://api.cloudflare.com/client/v4/accounts/${CF_ACCOUNT_ID}/storage/kv/namespaces/${CF_NAMESPACE_ID}/values/stable" \ + -H "Authorization: Bearer ${CF_API_TOKEN}" \ + -H "Content-Type: application/json" \ + -d "$JSON_DATA" + echo diff --git a/.github/workflows/update_versions.yml b/.github/workflows/update_versions.yml index 6bceead5e..e246f84c2 100644 --- a/.github/workflows/update_versions.yml +++ b/.github/workflows/update_versions.yml @@ -11,11 +11,14 @@ jobs: name: 'Update versions in Cloudflare KV store' runs-on: ubuntu-latest steps: - - name: Checkout repository uses: actions/checkout@v6 - + - name: Get and Send EMS-ESP version to Cloudflare + env: + CF_ACCOUNT_ID: ${{ secrets.CF_ACCOUNT_ID }} + CF_NAMESPACE_ID: ${{ secrets.CF_NAMESPACE_ID }} + CF_API_TOKEN: ${{ secrets.CF_API_TOKEN }} run: | version=$(grep -E '^#define EMSESP_APP_VERSION' ./src/emsesp_version.h | awk -F'"' '{print $2}') if [ "$GITHUB_REF" = "refs/heads/main" ]; then @@ -23,9 +26,15 @@ jobs: else KV_ENV="dev" fi - JSON_DATA="{\"version\": \"${version}\", \"date\": \"$(date -u +%Y-%m-%dT%H:%M:%SZ)\"}" - curl -X PUT "https://api.cloudflare.com/client/v4/accounts/${{ secrets.CF_ACCOUNT_ID }}/storage/kv/namespaces/${{ secrets.CF_NAMESPACE_ID }}/values/$KV_ENV" \ - -H "Authorization: Bearer ${{ secrets.CF_API_TOKEN }}" \ - -H "Content-Type: text/plain" \ - -d "$JSON_DATA" - \ No newline at end of file + JSON_DATA=$(jq -n \ + --arg version "$version" \ + --arg date "$(date -u +%Y-%m-%dT%H:%M:%SZ)" \ + '{version: $version, date: $date}') + echo "KV_ENV: $KV_ENV" + echo "JSON_DATA: $JSON_DATA" + curl -sS --fail-with-body \ + -X PUT "https://api.cloudflare.com/client/v4/accounts/${CF_ACCOUNT_ID}/storage/kv/namespaces/${CF_NAMESPACE_ID}/values/${KV_ENV}" \ + -H "Authorization: Bearer ${CF_API_TOKEN}" \ + -H "Content-Type: application/json" \ + -d "$JSON_DATA" + echo From 6802336b6b1a7f86f122f74350177a910f6f74ed Mon Sep 17 00:00:00 2001 From: proddy Date: Sun, 26 Apr 2026 16:07:29 +0200 Subject: [PATCH 08/48] remove old code --- src/test/test.cpp | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/src/test/test.cpp b/src/test/test.cpp index 4a7181893..1ea87e770 100644 --- a/src/test/test.cpp +++ b/src/test/test.cpp @@ -1329,16 +1329,6 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & cmd, const // request.url("/rest/action"); // EMSESP::webStatusService.action(&request, doc.as()); - // test version checks - // use same data as in restServer.ts - // log shows first if you can upgrade to dev, and then if you can upgrade to stable - // request.url("/rest/action"); - // std::string LATEST_STABLE_VERSION = "3.8.0"; - // std::string LATEST_DEV_VERSION = "3.8.1-dev.3"; - // std::string param = LATEST_DEV_VERSION + "," + LATEST_STABLE_VERSION; - // std::string action = "{\"action\":\"checkUpgrade\", \"param\":\"" + param + "\"}"; - // deserializeJson(doc, action); - // // case 0: on latest stable, can upgrade to dev only. So true, false // EMSESP::webStatusService.set_current_version(LATEST_STABLE_VERSION); // EMSESP::webStatusService.action(&request, doc.as()); From 74062bab57bf59ef06c7308d7caeab7d05fd36f1 Mon Sep 17 00:00:00 2001 From: proddy Date: Sun, 26 Apr 2026 16:07:45 +0200 Subject: [PATCH 09/48] update tests --- test/test_api/api_test.http | 5 ++--- test/test_api/api_test.sh | 28 +++++++++++++++++++--------- 2 files changed, 21 insertions(+), 12 deletions(-) diff --git a/test/test_api/api_test.http b/test/test_api/api_test.http index 1a37372bc..e8a1af255 100755 --- a/test/test_api/api_test.http +++ b/test/test_api/api_test.http @@ -3,9 +3,8 @@ # Open this file in VSC, modify the token, go to the API call and click on 'Send Request' (or Ctrl+Alt+R) # The response will be shown in the right panel -# @host = http://ems-esp.local -@host = http://192.168.1.223 -@host_dev = http://192.168.1.65 +@host = http://ems-esp.local +@host_dev = http://ems-espT.local @host_standalone = http://localhost:3080 @host_standalone2 = http://localhost:3082 diff --git a/test/test_api/api_test.sh b/test/test_api/api_test.sh index 8deaa3013..0de8cde3b 100755 --- a/test/test_api/api_test.sh +++ b/test/test_api/api_test.sh @@ -4,7 +4,8 @@ # Command line test for the API # -emsesp_url="http://192.168.1.223" +# emsesp_url="http://ems-esp.local" +emsesp_url="http://ems-espT.local" # get the token from the Security page. This is the token for the admin user, unless changed it'll always be the same emsesp_token="eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImFkbWluIiwiYWRtaW4iOnRydWV9.2bHpWya2C7Q12WjNUBD6_7N3RCD7CMl-EGhyQVzFdDg" @@ -40,13 +41,22 @@ curl -X POST \ echo "\n" +# Get all versions +curl -X POST \ + -H "Authorization: Bearer ${emsesp_token}" \ + -H "Content-Type: application/json" \ + -d '{"action":"getVersions"}' \ + ${emsesp_url}/rest/action + +echo "\n" + # This example is how to call a service in Home Assistant via the API # Which can be added to an EMS-EPS schedule - -ha_url="http://192.168.1.86:8123" -ha_token="eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiIwMzMyZjU1MjhlZmM0NGIyOTgyMjIxNThiODU1NDkyNSIsImlhdCI6MTcyMTMwNDg2NSwiZXhwIjoyMDM2NjY0ODY1fQ.Q-Y7E_i7clH3ff4Ma-OMmhZfbN7aMi_CahKwmoar" - -curl -X POST \ - ${ha_url}/api/services/script/test_notify \ - -H "Authorization: Bearer ${ha_token}" \ - -H "Content-Type: application/json" +# +# ha_url="http://192.168.1.86:8123" +# ha_token="eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiIwMzMyZjU1MjhlZmM0NGIyOTgyMjIxNThiODU1NDkyNSIsImlhdCI6MTcyMTMwNDg2NSwiZXhwIjoyMDM2NjY0ODY1fQ.Q-Y7E_i7clH3ff4Ma-OMmhZfbN7aMi_CahKwmoar" +# +# curl -X POST \ +# ${ha_url}/api/services/script/test_notify \ +# -H "Authorization: Bearer ${ha_token}" \ +# -H "Content-Type: application/json" From 3a11327e7ea7280a8cc18886b651ebb06ef1081c Mon Sep 17 00:00:00 2001 From: proddy Date: Sun, 26 Apr 2026 16:10:30 +0200 Subject: [PATCH 10/48] https://github.com/emsesp/EMS-ESP32/discussions/3044 --- interface/src/api/endpoints.ts | 3 - interface/src/api/system.ts | 10 +- interface/src/app/status/Version.tsx | 97 ++++++------- interface/vite.config.ts | 3 +- mock-api/restServer.ts | 107 +++++++------- src/core/emsesp.cpp | 1 + src/core/system.cpp | 7 +- src/web/WebStatusService.cpp | 200 +++++++++++++++++++++------ src/web/WebStatusService.h | 30 +++- 9 files changed, 290 insertions(+), 168 deletions(-) diff --git a/interface/src/api/endpoints.ts b/interface/src/api/endpoints.ts index e598d7aad..db867385a 100644 --- a/interface/src/api/endpoints.ts +++ b/interface/src/api/endpoints.ts @@ -57,6 +57,3 @@ export const alovaInstance = createAlova({ onSuccess: handleResponse } }); - -export const DOCS_BASE_URL = - process.env.NODE_ENV === 'development' ? '/emsesp.org' : 'https://emsesp.org'; diff --git a/interface/src/api/system.ts b/interface/src/api/system.ts index 6910c2df2..43829025a 100644 --- a/interface/src/api/system.ts +++ b/interface/src/api/system.ts @@ -1,6 +1,6 @@ import type { LogSettings, SystemStatus } from 'types'; -import { DOCS_BASE_URL, alovaInstance } from './endpoints'; +import { alovaInstance } from './endpoints'; // systemStatus - also used to ping in System Monitor for pinging export const readSystemStatus = () => @@ -13,14 +13,6 @@ export const updateLogSettings = (data: LogSettings) => alovaInstance.Post('/rest/logSettings', data); export const fetchLogES = () => alovaInstance.Get('/es/log'); -// get versions from emsesp.org/versions.json -// uses native fetch (no custom headers) to keep this as a "simple" CORS -export const getVersions = async (): Promise => { - const res = await fetch(`${DOCS_BASE_URL}/versions.json`); - if (!res.ok) throw new Error(res.statusText); - return res.json() as Promise; -}; - const UPLOAD_TIMEOUT = 60000; // 1 minute export const uploadFile = (file: File) => { diff --git a/interface/src/app/status/Version.tsx b/interface/src/app/status/Version.tsx index 64a5b1cd6..b2aa166fa 100644 --- a/interface/src/app/status/Version.tsx +++ b/interface/src/app/status/Version.tsx @@ -1,12 +1,4 @@ -import { - memo, - useCallback, - useContext, - useEffect, - useMemo, - useRef, - useState -} from 'react'; +import { memo, useCallback, useContext, useMemo, useState } from 'react'; import { Link } from 'react-router'; import { toast } from 'react-toastify'; @@ -34,7 +26,6 @@ import { import * as SystemApi from 'api/system'; import { API, callAction } from 'api/app'; -import { getVersions } from 'api/system'; import { dialogStyle } from 'CustomTheme'; import { useRequest } from 'alova/client'; @@ -79,21 +70,24 @@ interface VersionData { developer_mode: boolean; } -interface UpgradeCheckData { - emsesp_version: string; - dev_upgradeable: boolean; - stable_upgradeable: boolean; -} - interface VersionInfo { version: string; date: string; } -interface Versions { - stable: VersionInfo; - dev: VersionInfo; - last_updated: string; +interface RemoteVersionInfo extends VersionInfo { + upgradeable: boolean; +} + +interface CurrentVersionInfo extends VersionInfo { + type: 'stable' | 'dev'; +} + +// Response payload from the `getVersions` action +interface VersionsResponse { + current: CurrentVersionInfo; + stable?: RemoteVersionInfo; + dev?: RemoteVersionInfo; } // Memoized components for better performance @@ -465,15 +459,6 @@ const Version = () => { const [showVersionInfo, setShowVersionInfo] = useState(0); // 1 = stable, 2 = dev, 3 = partition const [firmwareSize, setFirmwareSize] = useState(0); - const { send: sendCheckUpgrade } = useRequest( - (versions: string) => callAction({ action: 'checkUpgrade', param: versions }), - { immediate: false } - ).onSuccess((event) => { - const data = event.data as UpgradeCheckData; - setDevUpgradeAvailable(data.dev_upgradeable); - setStableUpgradeAvailable(data.stable_upgradeable); - }); - const { send: sendSetPartition } = useRequest( (partition: string) => callAction({ action: 'setPartition', param: partition }), { immediate: false } @@ -490,7 +475,6 @@ const Version = () => { if (systemData.arduino_version.startsWith('Tasmota')) { setDownloadOnly(true); } - setUsingDevVersion(systemData.emsesp_version.includes('dev')); }); const { send: sendUploadURL } = useRequest( @@ -498,32 +482,33 @@ const Version = () => { { immediate: false } ); - // Fetch versions.json from emsesp.org once on mount. - // Uses plain fetch (not alova) so the request stays a "simple" CORS request and avoids a preflight that emsesp.org rejects. - // sendCheckUpgrade is stored in a ref because alova's useRequest returns a new function reference each render - const sendCheckUpgradeRef = useRef(sendCheckUpgrade); - sendCheckUpgradeRef.current = sendCheckUpgrade; - useEffect(() => { - let cancelled = false; - getVersions() - .then((versions) => { - if (cancelled) return; - setLatestVersion(versions.stable); - setLatestDevVersion(versions.dev); - sendCheckUpgradeRef.current( - `${versions.stable.version},${versions.dev.version}` - ); - setInternetLive(true); - }) - .catch((error: unknown) => { - if (cancelled) return; - toast.error(error instanceof Error ? error.message : 'An error occurred'); - setInternetLive(false); - }); - return () => { - cancelled = true; - }; - }, []); + // Fetch latest stable/dev versions via the device. The ESP32 calls + // emsesp.org/versions.json itself and includes its own `current` info plus + // upgradeable flags. If the device has no internet, `stable`/`dev` are + // absent and we surface that as "internet not live". + useRequest(() => callAction({ action: 'getVersions' })) + .onSuccess((event) => { + const versions = event.data as VersionsResponse; + setUsingDevVersion(versions.current?.type === 'dev'); + if (versions.stable) { + setLatestVersion({ + version: versions.stable.version, + date: versions.stable.date + }); + setStableUpgradeAvailable(versions.stable.upgradeable); + } + if (versions.dev) { + setLatestDevVersion({ + version: versions.dev.version, + date: versions.dev.date + }); + setDevUpgradeAvailable(versions.dev.upgradeable); + } + setInternetLive(Boolean(versions.stable || versions.dev)); + }) + .onError(() => { + setInternetLive(false); + }); const { send: sendAPI } = useRequest((data: APIcall) => API(data), { immediate: false diff --git a/interface/vite.config.ts b/interface/vite.config.ts index c8194c202..8bea7e00d 100644 --- a/interface/vite.config.ts +++ b/interface/vite.config.ts @@ -229,8 +229,7 @@ export default defineConfig( changeOrigin: true, secure: false }, - '/rest': 'http://localhost:3080', - '/emsesp.org': 'http://localhost:3080' + '/rest': 'http://localhost:3080' } }, build: { diff --git a/mock-api/restServer.ts b/mock-api/restServer.ts index 247827b09..0ac776333 100644 --- a/mock-api/restServer.ts +++ b/mock-api/restServer.ts @@ -6,7 +6,6 @@ const router = AutoRouter(); const REST_ENDPOINT_ROOT = '/rest/'; const API_ENDPOINT_ROOT = '/api/'; -const EMSESP_DOCS_ENDPOINT = '/emsesp.org/'; // for mock emsesp.org/versions.json // HTTP HEADERS for msgpack const headers = { @@ -302,10 +301,10 @@ function updateMask(entity: any, de: any, dd: any) { const old_custom_name = dd.nodes[dd_objIndex].cn; console.log( 'comparing names, old (' + - old_custom_name + - ') with new (' + - new_custom_name + - ')' + old_custom_name + + ') with new (' + + new_custom_name + + ')' ); if (old_custom_name !== new_custom_name) { changed = true; @@ -415,36 +414,54 @@ function upgradeImportantMessages(version: string) { return { upgradeImportantMessageType: upgradeImportantMessageType_n }; } -// called by Action endpoint checkUpgrade -function check_upgrade(version: string) { - let data = {}; - if (version) { - const dev_version = version.split(',')[0]; - const stable_version = version.split(',')[1]; +// called by Action endpoint getVersions +// Mirrors the C++ WebStatusService::getVersions() payload: +// { current: { version, type, date }, +// stable?: { version, date, upgradeable }, +// dev?: { version, date, upgradeable } } +// Set MOCK_OFFLINE = true to simulate a device with no internet (omits stable/dev). +const MOCK_OFFLINE = false; +function get_versions() { + const isDev = THIS_VERSION.includes('dev'); + const data: { + current: { version: string; type: 'stable' | 'dev'; date: string }; + stable?: { version: string; date: string; upgradeable: boolean }; + dev?: { version: string; date: string; upgradeable: boolean }; + } = { + current: { + version: THIS_VERSION, + type: isDev ? 'dev' : 'stable', + date: '2026-04-25T12:00:00' + } + }; - console.log( - 'Upgrade this version (' + - THIS_VERSION + - ') to dev (' + - dev_version + - ') is ' + - (DEV_VERSION_IS_UPGRADEABLE ? 'YES' : 'NO') + - ' and to stable (' + - stable_version + - ') is ' + - (STABLE_VERSION_IS_UPGRADEABLE ? 'YES' : 'NO') - ); - data = { - emsesp_version: THIS_VERSION, - dev_upgradeable: DEV_VERSION_IS_UPGRADEABLE, - stable_upgradeable: STABLE_VERSION_IS_UPGRADEABLE + if (!MOCK_OFFLINE) { + data.stable = { + version: LATEST_STABLE_VERSION, + date: '2026-04-25', + upgradeable: STABLE_VERSION_IS_UPGRADEABLE }; - } else { - console.log('requesting ems-esp version (' + THIS_VERSION + ')'); - data = { - emsesp_version: THIS_VERSION + data.dev = { + version: LATEST_DEV_VERSION, + date: '2026-04-25', + upgradeable: DEV_VERSION_IS_UPGRADEABLE }; } + + console.log( + 'getVersions: current=' + + THIS_VERSION + + ' stable=' + + LATEST_STABLE_VERSION + + ' (upgradeable=' + + (STABLE_VERSION_IS_UPGRADEABLE ? 'YES' : 'NO') + + ') dev=' + + LATEST_DEV_VERSION + + ' (upgradeable=' + + (DEV_VERSION_IS_UPGRADEABLE ? 'YES' : 'NO') + + ')' + + (MOCK_OFFLINE ? ' [offline]' : '') + ); return data; } @@ -706,7 +723,6 @@ const EMSESP_ACTION_ENDPOINT = REST_ENDPOINT_ROOT + 'action'; // these are used in the API calls only const EMSESP_SYSTEM_INFO_ENDPOINT = API_ENDPOINT_ROOT + 'system/info'; -const EMSESP_VERSIONS_ENDPOINT = EMSESP_DOCS_ENDPOINT + 'versions.json'; const emsesp_info = { System: { @@ -5173,13 +5189,9 @@ router } else if (action === 'getCustomSupport') { // send custom support return custom_support(); - } else if (action === 'checkUpgrade') { - // check upgrade - // check if content has a param - if (!content.param) { - return check_upgrade(''); - } - return check_upgrade(content.param); + } else if (action === 'getVersions') { + // get versions + return get_versions(); } else if (action === 'uploadURL') { // upload URL console.log('upload File from URL', content.param); @@ -5234,23 +5246,6 @@ router return status(404); // not found }); -// Mock emsesp.org/versions.json -router.get(EMSESP_VERSIONS_ENDPOINT, () => { - const data = { - stable: { - version: LATEST_STABLE_VERSION, - date: '2026-04-25' - }, - dev: { - version: LATEST_DEV_VERSION, - date: '2026-04-25' - }, - last_updated: new Date().toISOString() - }; - console.log('sending versions.json: ', data); - return data; -}); - // const logger: ResponseHandler = (response, request) => { // console.log( // response.status, diff --git a/src/core/emsesp.cpp b/src/core/emsesp.cpp index 9d583e427..dcd6f70da 100644 --- a/src/core/emsesp.cpp +++ b/src/core/emsesp.cpp @@ -1865,6 +1865,7 @@ void EMSESP::loop() { } // loop through the services + webStatusService.loop(); // periodic refresh of cached versions.json rxservice_.loop(); // process any incoming Rx telegrams shower_.loop(); // check for shower on/off temperaturesensor_.loop(); // read sensor temperatures diff --git a/src/core/system.cpp b/src/core/system.cpp index 45e5c6777..e2a8fb8ba 100644 --- a/src/core/system.cpp +++ b/src/core/system.cpp @@ -899,9 +899,7 @@ void System::heartbeat_json(JsonObject output) { #if CONFIG_IDF_TARGET_ESP32S3 || CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32S2 output["temperature"] = (int)temperature_; #endif -#endif -#ifndef EMSESP_STANDALONE if (!ethernet_connected_) { int8_t rssi = WiFi.RSSI(); output["rssi"] = rssi; @@ -909,6 +907,11 @@ void System::heartbeat_json(JsonObject output) { output["wifireconnects"] = EMSESP::esp32React.getWifiReconnects(); } #endif + + // see if there is a newer version available + if (EMSESP::webStatusService.versions_cache_valid()) { + output["upgradeable"] = EMSESP::webStatusService.current_upgradeable(); + } } // send periodic MQTT message with system information diff --git a/src/web/WebStatusService.cpp b/src/web/WebStatusService.cpp index d97c4d4e8..5cc062804 100644 --- a/src/web/WebStatusService.cpp +++ b/src/web/WebStatusService.cpp @@ -20,6 +20,7 @@ #ifndef EMSESP_STANDALONE #include +#include #endif namespace emsesp { @@ -205,11 +206,11 @@ void WebStatusService::action(AsyncWebServerRequest * request, JsonVariant json) bool is_admin = AuthenticationPredicates::IS_ADMIN(authentication); // call action command - bool ok = false; + bool ok = true; std::string action = json["action"]; - if (action == "checkUpgrade") { - ok = checkUpgrade(root, param); // param could be empty, if so only send back version + if (action == "getVersions") { + getVersions(root); } else if (action == "setPartition") { ok = EMSESP::system_.set_partition(param.c_str()); } else if (action == "export") { @@ -224,10 +225,8 @@ void WebStatusService::action(AsyncWebServerRequest * request, JsonVariant json) ok = setSystemStatus(param.c_str()); } else if (action == "resetMQTT" && is_admin) { EMSESP::mqtt_.reset_mqtt(); - ok = true; } else if (action == "upgradeImportantMessages") { root["upgradeImportantMessageType"] = upgradeImportantMessages(param); - ok = true; } #if defined(EMSESP_STANDALONE) && !defined(EMSESP_UNITY) @@ -311,46 +310,169 @@ uint8_t WebStatusService::upgradeImportantMessages(std::string & version) { return 0; // if it's not a valid version upgrade return 0 } -// action = checkUpgrade -// versions holds the latest development version and stable version in one string, comma separated -bool WebStatusService::checkUpgrade(JsonObject root, std::string & version) { - if (!version.empty()) { - version::EMSESP_Version current_version(current_version_s); - version::EMSESP_Version latest_dev_version(version.substr(0, version.find(','))); - version::EMSESP_Version latest_stable_version(version.substr(version.find(',') + 1)); +// action = getVersions +// returns the device's current version info plus the cached "stable" and "dev" +// entries from emsesp.org/versions.json. The remote fetch is NOT done here: it +// runs from the main loop task via WebStatusService::loop() so we never block +// the AsyncTCP callback (which has a tiny ~6 KB stack — far too small for an +// HTTPS handshake). If we have no cached data yet (no internet, fetch still +// pending, parse error) the "stable" and "dev" sections are simply omitted so +// the client can detect the offline case. +void WebStatusService::getVersions(JsonObject root) { + version::EMSESP_Version current_version(current_version_s); + bool is_dev = current_version.prerelease().find("dev") != std::string::npos; - bool dev_upgradeable = latest_dev_version > current_version; - bool stable_upgradeable = latest_stable_version > current_version; + JsonObject current = root["current"].to(); + current["version"] = current_version_s; + current["type"] = is_dev ? "dev" : "stable"; + current["date"] = ""; + current["upgradeable"] = current_upgradeable(); // false if cache not valid yet -#if defined(EMSESP_DEBUG) - // look for dev in the name to determine if we're using a dev release - bool using_dev_version = !current_version.prerelease().find("dev"); - EMSESP::logger() - .debug("Checking version upgrade. This version=%d.%d.%d-%s (%s),latest dev=%d.%d.%d-%s (%s upgradeable),latest stable=%d.%d.%d-%s (%s upgradeable)", - current_version.major(), - current_version.minor(), - current_version.patch(), - current_version.prerelease().c_str(), - using_dev_version ? "Dev" : "Stable", - latest_dev_version.major(), - latest_dev_version.minor(), - latest_dev_version.patch(), - latest_dev_version.prerelease().c_str(), - dev_upgradeable ? "is" : "is not", - latest_stable_version.major(), - latest_stable_version.minor(), - latest_stable_version.patch(), - latest_stable_version.prerelease().c_str(), - stable_upgradeable ? "is" : "is not"); -#endif - - root["dev_upgradeable"] = dev_upgradeable; - root["stable_upgradeable"] = stable_upgradeable; +#ifndef EMSESP_STANDALONE + // pull the install_date for the running partition (if known) + const esp_partition_t * running = esp_ota_get_running_partition(); + if (running != nullptr) { + const auto & info = EMSESP::system_.partition_info_; + auto it = info.find(running->label); + if (it != info.end() && it->second.install_date > 0) { + char time_string[25]; + time_t d = it->second.install_date; + strftime(time_string, sizeof(time_string), "%FT%T", localtime(&d)); + current["date"] = time_string; + } } - root["emsesp_version"] = current_version_s; // always send back current version + if (!versions_cache_valid_) { + // no successful fetch yet (no network, fetch pending, or parse error) + return; + } + // copies a cached entry into root[key]. The upgradeable bool was computed + // once during refresh_versions_cache() so we just read it here. + auto add_section = [&](const char * key, const VersionInfo & info) { + if (info.version.empty()) { + return; + } + JsonObject out = root[key].to(); + out["version"] = info.version; + out["date"] = info.date; + out["upgradeable"] = info.upgradeable; + }; + + add_section("stable", versions_stable_); + add_section("dev", versions_dev_); +#else + // standalone/test build: provide deterministic dummy data + JsonObject stable_out = root["stable"].to(); + stable_out["version"] = "3.8.2"; + stable_out["date"] = "2026-04-25"; + stable_out["upgradeable"] = version::EMSESP_Version("3.8.2") > current_version; + + JsonObject dev_out = root["dev"].to(); + dev_out["version"] = "3.8.3-dev.2"; + dev_out["date"] = "2026-04-25"; + dev_out["upgradeable"] = version::EMSESP_Version("3.8.3-dev.2") > current_version; +#endif +} + +// periodic refresh (1 hour) of the cached versions.json. Runs on the main loop task, +// which has a much bigger stack than AsyncTCP, so it's safe to do HTTPS here. +void WebStatusService::loop() { +#ifndef EMSESP_STANDALONE + // need a network + if (!EMSESP::system_.ethernet_connected() && (WiFi.status() != WL_CONNECTED)) { + return; + } + + uint32_t now = uuid::get_uptime(); + + // first call after we have a network: schedule the initial fetch a little + // later so we give NTP / DNS a chance to settle + if (versions_next_fetch_ms_ == 0) { + versions_next_fetch_ms_ = now + VERSIONS_INITIAL_DELAY_MS; + if (versions_next_fetch_ms_ == 0) { + versions_next_fetch_ms_ = 1; // avoid the "never scheduled" sentinel + } + return; + } + + // not time yet (signed difference handles uint32 wrap) + if ((int32_t)(now - versions_next_fetch_ms_) < 0) { + return; + } + + bool ok = refresh_versions_cache(); + + uint32_t next = uuid::get_uptime() + (ok ? VERSIONS_REFRESH_INTERVAL_MS : VERSIONS_RETRY_INTERVAL_MS); + if (next == 0) { + next = 1; + } + versions_next_fetch_ms_ = next; +#endif +} + +// runs on the main loop task — never call this from an AsyncWebServer handler +bool WebStatusService::refresh_versions_cache() { +#ifdef EMSESP_STANDALONE + return false; +#else + HTTPClient http; + http.setFollowRedirects(HTTPC_STRICT_FOLLOW_REDIRECTS); + http.setTimeout(5000); + http.useHTTP10(true); + + if (!http.begin("https://emsesp.org/versions.json")) { + EMSESP::logger().debug("versions.json: failed to start HTTPS request"); + return false; + } + + int httpCode = http.GET(); + if (httpCode != HTTP_CODE_OK) { + EMSESP::logger().debug("versions.json: HTTP %d", httpCode); + http.end(); + return false; + } + + JsonDocument doc; + DeserializationError err = deserializeJson(doc, http.getStream()); + http.end(); + if (err) { + EMSESP::logger().debug("versions.json: parse error (%s)", err.c_str()); + return false; + } + + version::EMSESP_Version current_version(current_version_s); + + auto read_section = [&doc, ¤t_version](const char * key, VersionInfo & out) { + JsonObjectConst section = doc[key]; + if (section.isNull()) { + out = {}; + return; + } + out.version = section["version"] | ""; + out.date = section["date"] | ""; + out.upgradeable = !out.version.empty() && version::EMSESP_Version(out.version) > current_version; + }; + + read_section("stable", versions_stable_); + read_section("dev", versions_dev_); + + versions_cache_valid_ = true; +#if defined(EMSESP_DEBUG) + EMSESP::logger().debug("versions.json refreshed (stable=%s dev=%s)", versions_stable_.version.c_str(), versions_dev_.version.c_str()); +#endif return true; +#endif +} + +// returns if current dev/stable is upgradeable +bool WebStatusService::current_upgradeable() const { + if (!versions_cache_valid_) { + return false; + } + version::EMSESP_Version current_version(current_version_s); + bool is_dev = current_version.prerelease().find("dev") != std::string::npos; + return is_dev ? versions_dev_.upgradeable : versions_stable_.upgradeable; } // action = allvalues diff --git a/src/web/WebStatusService.h b/src/web/WebStatusService.h index 6d3f59f1a..706d5d956 100644 --- a/src/web/WebStatusService.h +++ b/src/web/WebStatusService.h @@ -19,6 +19,17 @@ class WebStatusService { return current_version_s; } + // called from EMSESP::loop() to refresh the cached versions.json from emsesp.org so that the web + // request handler never has to do blocking HTTPS on the small AsyncTCP stack + void loop(); + + // true once we've had at least one successful versions.json fetch + bool versions_cache_valid() const { + return versions_cache_valid_; + } + + bool current_upgradeable() const; // true if a newer version is available + // make action function public so we can test in the debug and standalone mode #ifndef EMSESP_STANDALONE protected: @@ -30,7 +41,7 @@ class WebStatusService { SecurityManager * _securityManager; // actions - bool checkUpgrade(JsonObject root, std::string & latest_version); + void getVersions(JsonObject root); bool exportData(JsonObject root, std::string & type); bool getCustomSupport(JsonObject root); bool uploadURL(const char * url); @@ -39,6 +50,23 @@ class WebStatusService { uint8_t upgradeImportantMessages(std::string & version); std::string current_version_s = EMSESP_APP_VERSION; + + // cached emsesp.org/versions.json. Refreshed from the main loop task, which has more stack. + struct VersionInfo { + std::string version; + std::string date; + bool upgradeable = false; + }; + VersionInfo versions_stable_; + VersionInfo versions_dev_; + bool versions_cache_valid_ = false; // true once we've had at least one successful fetch + uint32_t versions_next_fetch_ms_ = 0; // uuid::get_uptime() of the next attempt; 0 = ASAP + + bool refresh_versions_cache(); // does the actual HTTPS fetch + parse, returns true on success + + static constexpr uint32_t VERSIONS_REFRESH_INTERVAL_MS = 60UL * 60UL * 1000UL; // 1 hour on success + static constexpr uint32_t VERSIONS_RETRY_INTERVAL_MS = 5UL * 60UL * 1000UL; // 5 min after failure + static constexpr uint32_t VERSIONS_INITIAL_DELAY_MS = 30UL * 1000UL; // wait 30s after boot }; } // namespace emsesp From 1107e1bdf31b6e99778b022f71ed8759a25cb06a Mon Sep 17 00:00:00 2001 From: proddy Date: Sun, 26 Apr 2026 16:10:35 +0200 Subject: [PATCH 11/48] package update --- interface/package.json | 11 +--- interface/pnpm-lock.yaml | 119 +++------------------------------------ 2 files changed, 10 insertions(+), 120 deletions(-) diff --git a/interface/package.json b/interface/package.json index ce234abf8..8bace3c1d 100644 --- a/interface/package.json +++ b/interface/package.json @@ -28,14 +28,11 @@ "@emotion/styled": "^11.14.1", "@mui/icons-material": "^9.0.0", "@mui/material": "^9.0.0", - "@preact/compat": "^18.3.2", "@table-library/react-table-library": "4.1.15", "alova": "3.5.1", "async-validator": "^4.2.5", "etag": "^1.8.1", - "formidable": "^3.5.4", "jwt-decode": "^4.0.0", - "magic-string": "^0.30.21", "mime-types": "^3.0.2", "preact": "^10.29.1", "react": "^19.2.5", @@ -47,16 +44,13 @@ "typescript": "^6.0.3" }, "devDependencies": { - "@babel/core": "^7.29.0", "@eslint/js": "^10.0.1", - "@preact/compat": "^18.3.2", "@preact/preset-vite": "^2.10.5", - "@trivago/prettier-plugin-sort-imports": "^6.0.2", "@types/node": "^25.6.0", "@types/react": "^19.2.14", "@types/react-dom": "^19.2.3", - "axe-core": "^4.11.3", "concurrently": "^9.2.1", + "@trivago/prettier-plugin-sort-imports": "^6.0.2", "eslint": "^10.2.1", "eslint-config-prettier": "^10.1.8", "prettier": "^3.8.3", @@ -64,8 +58,7 @@ "terser": "^5.46.2", "typescript-eslint": "^8.59.0", "vite": "^8.0.10", - "vite-plugin-imagemin": "^0.6.1", - "vite-tsconfig-paths": "^6.1.1" + "vite-plugin-imagemin": "^0.6.1" }, "packageManager": "pnpm@10.33.2+sha512.a90faf6feeab71ad6c6e57f94e0fe1a12f5dcc22cd754db40ae9593eb6a3e0b6b12e3540218bb37ae083404b1f2ce6db2a4121e979829b4aff94b99f49da1cf8" } diff --git a/interface/pnpm-lock.yaml b/interface/pnpm-lock.yaml index 5df136395..c0ba3f7e6 100644 --- a/interface/pnpm-lock.yaml +++ b/interface/pnpm-lock.yaml @@ -23,9 +23,6 @@ importers: '@mui/material': specifier: ^9.0.0 version: 9.0.0(@emotion/react@11.14.0(@types/react@19.2.14)(react@19.2.5))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.14)(react@19.2.5))(@types/react@19.2.14)(react@19.2.5))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@preact/compat': - specifier: ^18.3.2 - version: 18.3.2(preact@10.29.1) '@table-library/react-table-library': specifier: 4.1.15 version: 4.1.15(@emotion/react@11.14.0(@types/react@19.2.14)(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5) @@ -38,15 +35,9 @@ importers: etag: specifier: ^1.8.1 version: 1.8.1 - formidable: - specifier: ^3.5.4 - version: 3.5.4 jwt-decode: specifier: ^4.0.0 version: 4.0.0 - magic-string: - specifier: ^0.30.21 - version: 0.30.21 mime-types: specifier: ^3.0.2 version: 3.0.2 @@ -75,9 +66,6 @@ importers: specifier: ^6.0.3 version: 6.0.3 devDependencies: - '@babel/core': - specifier: ^7.29.0 - version: 7.29.0 '@eslint/js': specifier: ^10.0.1 version: 10.0.1(eslint@10.2.1) @@ -96,9 +84,6 @@ importers: '@types/react-dom': specifier: ^19.2.3 version: 19.2.3(@types/react@19.2.14) - axe-core: - specifier: ^4.11.3 - version: 4.11.3 concurrently: specifier: ^9.2.1 version: 9.2.1 @@ -126,9 +111,6 @@ importers: vite-plugin-imagemin: specifier: ^0.6.1 version: 0.6.1(vite@8.0.10(@types/node@25.6.0)(esbuild@0.27.4)(terser@5.46.2)) - vite-tsconfig-paths: - specifier: ^6.1.1 - version: 6.1.1(typescript@6.0.3)(vite@8.0.10(@types/node@25.6.0)(esbuild@0.27.4)(terser@5.46.2)) packages: @@ -637,10 +619,6 @@ packages: '@emnapi/core': ^1.7.1 '@emnapi/runtime': ^1.7.1 - '@noble/hashes@1.8.0': - resolution: {integrity: sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==} - engines: {node: ^14.21.3 || >=16} - '@nodelib/fs.scandir@2.1.5': resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} engines: {node: '>= 8'} @@ -656,17 +634,9 @@ packages: '@oxc-project/types@0.127.0': resolution: {integrity: sha512-aIYXQBo4lCbO4z0R3FHeucQHpF46l2LbMdxRvqvuRuW2OxdnSkcng5B8+K12spgLDj93rtN3+J2Vac/TIO+ciQ==} - '@paralleldrive/cuid2@2.3.1': - resolution: {integrity: sha512-XO7cAxhnTZl0Yggq6jOgjiOHhbgcO4NqFqwSmQpjK3b6TEE6Uj/jfSk6wzYyemh3+I0sHirKSetjQwn5cZktFw==} - '@popperjs/core@2.11.8': resolution: {integrity: sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==} - '@preact/compat@18.3.2': - resolution: {integrity: sha512-5vSl55K5yLMvocT7PBKxDOHGgYPjMrKQqqr6roSNjIXcJOtSgDDMjpiCAF3s7klRdmGrN75b/Przmjw8gmlg/w==} - peerDependencies: - preact: '*' - '@preact/preset-vite@2.10.5': resolution: {integrity: sha512-p0vJpxiVO7KWWazWny3LUZ+saXyZKWv6Ju0bYMWNJRp2YveufRPgSUB1C4MTqGJfz07EehMgfN+AJNwQy+w6Iw==} peerDependencies: @@ -1155,9 +1125,6 @@ packages: resolution: {integrity: sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==} engines: {node: '>=8'} - asap@2.0.6: - resolution: {integrity: sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==} - async-validator@4.2.5: resolution: {integrity: sha512-7HhHjtERjqlNbZtqNqy2rckN/SpOOlmDliet+lP7k+eKZEjPk3DgyeU9lIXLdeLz0uBbbVp+9Qdow9wJWgwwfg==} @@ -1165,10 +1132,6 @@ packages: resolution: {integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==} engines: {node: '>= 0.4'} - axe-core@4.11.3: - resolution: {integrity: sha512-zBQouZixDTbo3jMGqHKyePxYxr1e5W8UdTmBQ7sNtaA9M2bE32daxxPLS/jojhKOHxQ7LWwPjfiwf/fhaJWzlg==} - engines: {node: '>=4'} - babel-plugin-macros@3.1.0: resolution: {integrity: sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg==} engines: {node: '>=10', npm: '>=6'} @@ -1287,8 +1250,8 @@ packages: resolution: {integrity: sha512-DLIsRzJVBQu72meAKPkWQOLcujdXT32hwdfnkI1frSiSRMK1MofjKHf+MEx0SB6fjEFXL8fBDv1dKymBlOp4Qw==} engines: {node: '>=0.10.0'} - caniuse-lite@1.0.30001790: - resolution: {integrity: sha512-bOoxfJPyYo+ds6W0YfptaCWbFnJYjh2Y1Eow5lRv+vI2u8ganPZqNm1JwNh0t2ELQCqIWg4B3dWEusgAmsoyOw==} + caniuse-lite@1.0.30001791: + resolution: {integrity: sha512-yk0l/YSrOnFZk3UROpDLQD9+kC1l4meK/wed583AXrzoarMGJcbRi2Q4RaUYbKxYAsZ8sWmaSa/DsLmdBeI1vQ==} caw@2.0.1: resolution: {integrity: sha512-Cg8/ZSBEa8ZVY9HspcGUYaK63d/bN7rqS3CYCzEGUxuYv6UlmcjzDUz2fCFFHyTvUW5Pk0I+3hkA3iXlIj6guA==} @@ -1475,9 +1438,6 @@ packages: resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} engines: {node: '>=8'} - dezalgo@1.0.4: - resolution: {integrity: sha512-rXSP0bf+5n0Qonsb+SVVfNfIsimO4HEtmnIpPHY8Q1UCzKlQrDMfdobr8nJOOsRgWCyMRqeSBQzmWUMq7zvVig==} - dir-glob@3.0.1: resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} engines: {node: '>=8'} @@ -1892,10 +1852,6 @@ packages: resolution: {integrity: sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==} engines: {node: '>= 0.4'} - formidable@3.5.4: - resolution: {integrity: sha512-YikH+7CUTOtP44ZTnUhR7Ic2UASBPOqmaRkRKxRbywPTe5VxF7RRCck4af9wutiZ/QKM5nME9Bie2fFaPz5Gug==} - engines: {node: '>=14.0.0'} - from2@2.3.0: resolution: {integrity: sha512-OMcX/4IC/uqEPVgGeyfN22LJk6AZrMkRZHxcHBMBvHScDGgwTm2GT2Wkgtocyd3JfZffjj2kYUDXXII0Fk9W0g==} @@ -1986,9 +1942,6 @@ packages: resolution: {integrity: sha512-7dUi7RvCoT/xast/o/dLN53oqND4yk0nsHkhRgn9w65C4PofCLOoJ39iSOg+qVDdWQPIEj+eszMHQ+aLVwwQSg==} engines: {node: '>=8'} - globrex@0.1.2: - resolution: {integrity: sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg==} - gopd@1.2.0: resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} engines: {node: '>= 0.4'} @@ -2721,8 +2674,8 @@ packages: resolution: {integrity: sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==} engines: {node: '>= 0.4'} - postcss@8.5.10: - resolution: {integrity: sha512-pMMHxBOZKFU6HgAZ4eyGnwXF/EvPGGqUr0MnZ5+99485wwW41kW91A4LOGxSHhgugZmSChL5AlElNdwlNgcnLQ==} + postcss@8.5.11: + resolution: {integrity: sha512-5dDj8+lmvA8XB78SmzGI8NlQoksv7IfutGWeVZxiixHbO+p4LDPT3wuG/D9sM/wrjZZ9I+Siy/e117vbFPxSZg==} engines: {node: ^10 || ^12 || >=14} powershell-utils@0.1.0: @@ -3176,16 +3129,6 @@ packages: peerDependencies: typescript: '>=4.8.4' - tsconfck@3.1.6: - resolution: {integrity: sha512-ks6Vjr/jEw0P1gmOVwutM3B7fWxoWBL2KRDb1JfqGVawBmO5UsvmWOQFGHBPl5yxYz4eERr19E6L7NMv+Fej4w==} - engines: {node: ^18 || >=20} - hasBin: true - peerDependencies: - typescript: ^5.0.0 - peerDependenciesMeta: - typescript: - optional: true - tslib@2.8.1: resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} @@ -3274,11 +3217,6 @@ packages: peerDependencies: vite: 5.x || 6.x || 7.x || 8.x - vite-tsconfig-paths@6.1.1: - resolution: {integrity: sha512-2cihq7zliibCCZ8P9cKJrQBkfgdvcFkOOc3Y02o3GWUDLgqjWsZudaoiuOwO/gzTzy17cS5F7ZPo4bsnS4DGkg==} - peerDependencies: - vite: '*' - vite@8.0.10: resolution: {integrity: sha512-rZuUu9j6J5uotLDs+cAA4O5H4K1SfPliUlQwqa6YEwSrWDZzP4rhm00oJR5snMewjxF5V/K3D4kctsUTsIU9Mw==} engines: {node: ^20.19.0 || >=22.12.0} @@ -3886,8 +3824,6 @@ snapshots: '@tybys/wasm-util': 0.10.1 optional: true - '@noble/hashes@1.8.0': {} - '@nodelib/fs.scandir@2.1.5': dependencies: '@nodelib/fs.stat': 2.0.5 @@ -3902,16 +3838,8 @@ snapshots: '@oxc-project/types@0.127.0': {} - '@paralleldrive/cuid2@2.3.1': - dependencies: - '@noble/hashes': 1.8.0 - '@popperjs/core@2.11.8': {} - '@preact/compat@18.3.2(preact@10.29.1)': - dependencies: - preact: 10.29.1 - '@preact/preset-vite@2.10.5(@babel/core@7.29.0)(preact@10.29.1)(rollup@4.59.0)(vite@8.0.10(@types/node@25.6.0)(esbuild@0.27.4)(terser@5.46.2))': dependencies: '@babel/core': 7.29.0 @@ -4329,16 +4257,12 @@ snapshots: array-union@2.1.0: {} - asap@2.0.6: {} - async-validator@4.2.5: {} available-typed-arrays@1.0.7: dependencies: possible-typed-array-names: 1.1.0 - axe-core@4.11.3: {} - babel-plugin-macros@3.1.0: dependencies: '@babel/runtime': 7.29.2 @@ -4417,7 +4341,7 @@ snapshots: browserslist@4.28.2: dependencies: baseline-browser-mapping: 2.10.22 - caniuse-lite: 1.0.30001790 + caniuse-lite: 1.0.30001791 electron-to-chromium: 1.5.344 node-releases: 2.0.38 update-browserslist-db: 1.2.3(browserslist@4.28.2) @@ -4480,7 +4404,7 @@ snapshots: camelcase@2.1.1: {} - caniuse-lite@1.0.30001790: {} + caniuse-lite@1.0.30001791: {} caw@2.0.1: dependencies: @@ -4697,11 +4621,6 @@ snapshots: detect-libc@2.1.2: {} - dezalgo@1.0.4: - dependencies: - asap: 2.0.6 - wrappy: 1.0.2 - dir-glob@3.0.1: dependencies: path-type: 4.0.0 @@ -5157,12 +5076,6 @@ snapshots: dependencies: is-callable: 1.2.7 - formidable@3.5.4: - dependencies: - '@paralleldrive/cuid2': 2.3.1 - dezalgo: 1.0.4 - once: 1.4.0 - from2@2.3.0: dependencies: inherits: 2.0.4 @@ -5265,8 +5178,6 @@ snapshots: merge2: 1.4.1 slash: 3.0.0 - globrex@0.1.2: {} - gopd@1.2.0: {} got@7.1.0: @@ -5940,7 +5851,7 @@ snapshots: possible-typed-array-names@1.1.0: {} - postcss@8.5.10: + postcss@8.5.11: dependencies: nanoid: 3.3.11 picocolors: 1.1.1 @@ -6400,10 +6311,6 @@ snapshots: dependencies: typescript: 6.0.3 - tsconfck@3.1.6(typescript@6.0.3): - optionalDependencies: - typescript: 6.0.3 - tslib@2.8.1: {} tunnel-agent@0.6.0: @@ -6516,21 +6423,11 @@ snapshots: stack-trace: 1.0.0-pre2 vite: 8.0.10(@types/node@25.6.0)(esbuild@0.27.4)(terser@5.46.2) - vite-tsconfig-paths@6.1.1(typescript@6.0.3)(vite@8.0.10(@types/node@25.6.0)(esbuild@0.27.4)(terser@5.46.2)): - dependencies: - debug: 4.4.3 - globrex: 0.1.2 - tsconfck: 3.1.6(typescript@6.0.3) - vite: 8.0.10(@types/node@25.6.0)(esbuild@0.27.4)(terser@5.46.2) - transitivePeerDependencies: - - supports-color - - typescript - vite@8.0.10(@types/node@25.6.0)(esbuild@0.27.4)(terser@5.46.2): dependencies: lightningcss: 1.32.0 picomatch: 4.0.4 - postcss: 8.5.10 + postcss: 8.5.11 rolldown: 1.0.0-rc.17 tinyglobby: 0.2.16 optionalDependencies: From d834d4658610468aa5dffd4e2fb191d83bf66d67 Mon Sep 17 00:00:00 2001 From: proddy Date: Mon, 27 Apr 2026 11:08:13 +0200 Subject: [PATCH 12/48] rename EMSESP_Version to firmwareVersion --- src/core/EMSESP_Version.h | 126 ----------------------------------- src/core/firmwareVersion.cpp | 100 +++++++++++++++++++++++++++ src/core/firmwareVersion.h | 59 ++++++++++++++++ src/core/system.cpp | 6 +- 4 files changed, 162 insertions(+), 129 deletions(-) delete mode 100644 src/core/EMSESP_Version.h create mode 100644 src/core/firmwareVersion.cpp create mode 100644 src/core/firmwareVersion.h diff --git a/src/core/EMSESP_Version.h b/src/core/EMSESP_Version.h deleted file mode 100644 index 0803be2b5..000000000 --- a/src/core/EMSESP_Version.h +++ /dev/null @@ -1,126 +0,0 @@ -/* - * EMS-ESP - https://github.com/emsesp/EMS-ESP - * Copyright 2020-2026 emsesp.org - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -#ifndef EMSESP_Version_H -#define EMSESP_Version_H - -#include -#include -#include - -// Drop-in lightweight replacement for the subset of the semver library actually used by EMS-ESP. -// The previous semver library (lib/semver) builds a std::map + std::function-based state machine on -// every parse, which fragments the internal heap on the ESP32. This replacement does no heap -// allocation beyond the std::string member for the prerelease tag, and matches the API surface -// we consume: construction from string, major()/minor()/patch()/prerelease(), and operator>/(const EMSESP_Version & a, const EMSESP_Version & b) { - return b < a; - } - - friend bool operator==(const EMSESP_Version & a, const EMSESP_Version & b) { - return a.major_ == b.major_ && a.minor_ == b.minor_ && a.patch_ == b.patch_ && a.prerelease_ == b.prerelease_; - } - - friend bool operator!=(const EMSESP_Version & a, const EMSESP_Version & b) { - return !(a == b); - } - - friend bool operator>=(const EMSESP_Version & a, const EMSESP_Version & b) { - return !(a < b); - } - - friend bool operator<=(const EMSESP_Version & a, const EMSESP_Version & b) { - return !(b < a); - } - - private: - int major_ = 0; - int minor_ = 0; - int patch_ = 0; - std::string prerelease_; - - void parse(const char * s) { - major_ = minor_ = patch_ = 0; - prerelease_.clear(); - if (s == nullptr || *s == '\0') { - return; - } - // parse numeric major.minor.patch; accept partial ("3", "3.9", "3.9.0") - sscanf(s, "%d.%d.%d", &major_, &minor_, &patch_); - // capture prerelease tag after '-' if present (stop at '+' which is build metadata) - const char * dash = strchr(s, '-'); - if (dash != nullptr) { - const char * plus = strchr(dash, '+'); - if (plus != nullptr) { - prerelease_.assign(dash + 1, plus - dash - 1); - } else { - prerelease_.assign(dash + 1); - } - } - } -}; - -} // namespace version - -#endif diff --git a/src/core/firmwareVersion.cpp b/src/core/firmwareVersion.cpp new file mode 100644 index 000000000..de2aed742 --- /dev/null +++ b/src/core/firmwareVersion.cpp @@ -0,0 +1,100 @@ +/* + * EMS-ESP - https://github.com/emsesp/EMS-ESP + * Copyright 2020-2026 emsesp.org + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "firmwareVersion.h" + +#include +#include + +namespace emsesp { + +FirmwareVersion::FirmwareVersion(const std::string & s) { + parse(s.c_str()); +} + +FirmwareVersion::FirmwareVersion(const char * s) { + parse(s ? s : ""); +} + +int FirmwareVersion::major() const { + return major_; +} + +int FirmwareVersion::minor() const { + return minor_; +} + +int FirmwareVersion::patch() const { + return patch_; +} + +const std::string & FirmwareVersion::prerelease() const { + return prerelease_; +} + +bool operator<(const FirmwareVersion & a, const FirmwareVersion & b) { + if (a.major_ != b.major_) + return a.major_ < b.major_; + if (a.minor_ != b.minor_) + return a.minor_ < b.minor_; + if (a.patch_ != b.patch_) + return a.patch_ < b.patch_; + return a.prerelease_ < b.prerelease_; +} + +bool operator>(const FirmwareVersion & a, const FirmwareVersion & b) { + return b < a; +} + +bool operator==(const FirmwareVersion & a, const FirmwareVersion & b) { + return a.major_ == b.major_ && a.minor_ == b.minor_ && a.patch_ == b.patch_ && a.prerelease_ == b.prerelease_; +} + +bool operator!=(const FirmwareVersion & a, const FirmwareVersion & b) { + return !(a == b); +} + +bool operator>=(const FirmwareVersion & a, const FirmwareVersion & b) { + return !(a < b); +} + +bool operator<=(const FirmwareVersion & a, const FirmwareVersion & b) { + return !(b < a); +} + +void FirmwareVersion::parse(const char * s) { + major_ = minor_ = patch_ = 0; + prerelease_.clear(); + if (s == nullptr || *s == '\0') { + return; + } + // parse numeric major.minor.patch; accept partial ("3", "3.9", "3.9.0") + sscanf(s, "%d.%d.%d", &major_, &minor_, &patch_); + // capture prerelease tag after '-' if present (stop at '+' which is build metadata) + const char * dash = strchr(s, '-'); + if (dash != nullptr) { + const char * plus = strchr(dash, '+'); + if (plus != nullptr) { + prerelease_.assign(dash + 1, plus - dash - 1); + } else { + prerelease_.assign(dash + 1); + } + } +} + +} // namespace emsesp diff --git a/src/core/firmwareVersion.h b/src/core/firmwareVersion.h new file mode 100644 index 000000000..8076b14c5 --- /dev/null +++ b/src/core/firmwareVersion.h @@ -0,0 +1,59 @@ +/* + * EMS-ESP - https://github.com/emsesp/EMS-ESP + * Copyright 2020-2026 emsesp.org + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef firmwareVersion_H +#define firmwareVersion_H + +#include + +namespace emsesp { + +class FirmwareVersion { + public: + FirmwareVersion() = default; + + // Construct from a version string like "3.9.0-dev.14" or "3.9.0". + // Anything past a '-' or '+' is kept as the prerelease string and not interpreted. + explicit FirmwareVersion(const std::string & s); + explicit FirmwareVersion(const char * s); + + int major() const; + int minor() const; + int patch() const; + const std::string & prerelease() const; + + // Numeric-only comparison (major.minor.patch). Prerelease tags are ignored on purpose. + friend bool operator<(const FirmwareVersion & a, const FirmwareVersion & b); + friend bool operator>(const FirmwareVersion & a, const FirmwareVersion & b); + friend bool operator==(const FirmwareVersion & a, const FirmwareVersion & b); + friend bool operator!=(const FirmwareVersion & a, const FirmwareVersion & b); + friend bool operator>=(const FirmwareVersion & a, const FirmwareVersion & b); + friend bool operator<=(const FirmwareVersion & a, const FirmwareVersion & b); + + private: + int major_ = 0; + int minor_ = 0; + int patch_ = 0; + std::string prerelease_; + + void parse(const char * s); +}; + +} // namespace emsesp + +#endif diff --git a/src/core/system.cpp b/src/core/system.cpp index e2a8fb8ba..f3ab8073d 100644 --- a/src/core/system.cpp +++ b/src/core/system.cpp @@ -31,7 +31,7 @@ #include #include -#include "EMSESP_Version.h" +#include "firmwareVersion.h" #if defined(EMSESP_TEST) #include "../test/test.h" @@ -1541,8 +1541,8 @@ bool System::check_upgrade() { settingsVersion = "3.5.0"; // this was the last stable version without version info } - version::EMSESP_Version settings_version(settingsVersion); - version::EMSESP_Version this_version(EMSESP_APP_VERSION); + FirmwareVersion settings_version(settingsVersion); + FirmwareVersion this_version(EMSESP_APP_VERSION); std::string settings_version_type = settings_version.prerelease().empty() ? "" : ("-" + settings_version.prerelease()); std::string this_version_type = this_version.prerelease().empty() ? "" : ("-" + this_version.prerelease()); From 1cff1abc3306e5b68cfebcb1a9bd782cc0063306 Mon Sep 17 00:00:00 2001 From: proddy Date: Mon, 27 Apr 2026 11:08:22 +0200 Subject: [PATCH 13/48] package update --- interface/pnpm-lock.yaml | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/interface/pnpm-lock.yaml b/interface/pnpm-lock.yaml index c0ba3f7e6..e2032919d 100644 --- a/interface/pnpm-lock.yaml +++ b/interface/pnpm-lock.yaml @@ -1151,8 +1151,8 @@ packages: base64-js@1.5.1: resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} - baseline-browser-mapping@2.10.22: - resolution: {integrity: sha512-6qruVrb5rse6WylFkU0FhBKKGuecWseqdpQfhkawn6ztyk2QlfwSRjsDxMCLJrkfmfN21qvhl9ABgaMeRkuwww==} + baseline-browser-mapping@2.10.23: + resolution: {integrity: sha512-xwVXGqevyKPsiuQdLj+dZMVjidjJV508TBqexND5HrF89cGdCYCJFB3qhcxRHSeMctdCfbR1jrxBajhDy7o29g==} engines: {node: '>=6.0.0'} hasBin: true @@ -2674,8 +2674,8 @@ packages: resolution: {integrity: sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==} engines: {node: '>= 0.4'} - postcss@8.5.11: - resolution: {integrity: sha512-5dDj8+lmvA8XB78SmzGI8NlQoksv7IfutGWeVZxiixHbO+p4LDPT3wuG/D9sM/wrjZZ9I+Siy/e117vbFPxSZg==} + postcss@8.5.12: + resolution: {integrity: sha512-W62t/Se6rA0Az3DfCL0AqJwXuKwBeYg6nOaIgzP+xZ7N5BFCI7DYi1qs6ygUYT6rvfi6t9k65UMLJC+PHZpDAA==} engines: {node: ^10 || ^12 || >=14} powershell-utils@0.1.0: @@ -4279,7 +4279,7 @@ snapshots: base64-js@1.5.1: {} - baseline-browser-mapping@2.10.22: {} + baseline-browser-mapping@2.10.23: {} bin-build@3.0.0: dependencies: @@ -4340,7 +4340,7 @@ snapshots: browserslist@4.28.2: dependencies: - baseline-browser-mapping: 2.10.22 + baseline-browser-mapping: 2.10.23 caniuse-lite: 1.0.30001791 electron-to-chromium: 1.5.344 node-releases: 2.0.38 @@ -5851,7 +5851,7 @@ snapshots: possible-typed-array-names@1.1.0: {} - postcss@8.5.11: + postcss@8.5.12: dependencies: nanoid: 3.3.11 picocolors: 1.1.1 @@ -6427,7 +6427,7 @@ snapshots: dependencies: lightningcss: 1.32.0 picomatch: 4.0.4 - postcss: 8.5.11 + postcss: 8.5.12 rolldown: 1.0.0-rc.17 tinyglobby: 0.2.16 optionalDependencies: From 7c6259ddddef244078b604c7b830483778bf5048 Mon Sep 17 00:00:00 2001 From: proddy Date: Mon, 27 Apr 2026 11:08:52 +0200 Subject: [PATCH 14/48] tidy up comments --- interface/src/app/status/Version.tsx | 6 ++---- mock-api/restServer.ts | 4 ---- src/core/emsesp.cpp | 2 +- 3 files changed, 3 insertions(+), 9 deletions(-) diff --git a/interface/src/app/status/Version.tsx b/interface/src/app/status/Version.tsx index b2aa166fa..f434b6dec 100644 --- a/interface/src/app/status/Version.tsx +++ b/interface/src/app/status/Version.tsx @@ -482,10 +482,8 @@ const Version = () => { { immediate: false } ); - // Fetch latest stable/dev versions via the device. The ESP32 calls - // emsesp.org/versions.json itself and includes its own `current` info plus - // upgradeable flags. If the device has no internet, `stable`/`dev` are - // absent and we surface that as "internet not live". + // fetch latest stable/dev versions via the device. The C++ code makes a call to emsesp.org/versions.json itself + // if the device has no internet, stable/dev are omitted and the internetLive flag is set to false useRequest(() => callAction({ action: 'getVersions' })) .onSuccess((event) => { const versions = event.data as VersionsResponse; diff --git a/mock-api/restServer.ts b/mock-api/restServer.ts index 0ac776333..f8cb3bd15 100644 --- a/mock-api/restServer.ts +++ b/mock-api/restServer.ts @@ -415,10 +415,6 @@ function upgradeImportantMessages(version: string) { } // called by Action endpoint getVersions -// Mirrors the C++ WebStatusService::getVersions() payload: -// { current: { version, type, date }, -// stable?: { version, date, upgradeable }, -// dev?: { version, date, upgradeable } } // Set MOCK_OFFLINE = true to simulate a device with no internet (omits stable/dev). const MOCK_OFFLINE = false; function get_versions() { diff --git a/src/core/emsesp.cpp b/src/core/emsesp.cpp index dcd6f70da..38303a3ec 100644 --- a/src/core/emsesp.cpp +++ b/src/core/emsesp.cpp @@ -1811,10 +1811,10 @@ void EMSESP::start() { analogsensor_.start(factory_settings); // Analog external sensors // start web services + LOG_INFO("Starting Web Server"); webLogService.start(); // apply settings to weblog service webModulesService.begin(); // setup the external library modules webServer.begin(); // start the web server - LOG_INFO("Starting Web Server"); } void EMSESP::start_serial_console() { From 9ac35e2e14852d7127e552cb578cf65b4a11e7e3 Mon Sep 17 00:00:00 2001 From: proddy Date: Mon, 27 Apr 2026 11:09:24 +0200 Subject: [PATCH 15/48] fetch emsesp firmware versions after IP connected --- src/ESP32React/NetworkSettingsService.cpp | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/ESP32React/NetworkSettingsService.cpp b/src/ESP32React/NetworkSettingsService.cpp index ffb6275c9..5aa6b4218 100644 --- a/src/ESP32React/NetworkSettingsService.cpp +++ b/src/ESP32React/NetworkSettingsService.cpp @@ -321,6 +321,7 @@ void NetworkSettingsService::WiFiEvent(WiFiEvent_t event, WiFiEventInfo_t info) WiFi.localIP().toString().c_str(), WiFi.getHostname(), emsesp::Helpers::render_value(result, ((double)(WiFi.getTxPower()) / 4), 1)); + emsesp::EMSESP::webStatusService.schedule_versions_refresh(); // run the version fetch as soon as the main loop picks it up mDNS_start(); break; @@ -337,6 +338,7 @@ void NetworkSettingsService::WiFiEvent(WiFiEvent_t event, WiFiEventInfo_t info) if (!emsesp::EMSESP::system_.ethernet_connected()) { emsesp::EMSESP::logger().info("Ethernet connected (Local IP=%s, speed %d Mbps)", ETH.localIP().toString().c_str(), ETH.linkSpeed()); emsesp::EMSESP::system_.ethernet_connected(true); + emsesp::EMSESP::webStatusService.schedule_versions_refresh(); // run the version fetch as soon as the main loop picks it up mDNS_start(); } break; @@ -380,13 +382,15 @@ void NetworkSettingsService::WiFiEvent(WiFiEvent_t event, WiFiEventInfo_t info) auto ip6 = IPAddress(IPv6, (uint8_t *)info.got_ip6.ip6_info.ip.addr, 0).toString(); #endif const char * link = event == ARDUINO_EVENT_ETH_GOT_IP6 ? "Eth" : "WiFi"; +#if defined(EMSESP_DEBUG) if (ip6.startsWith("fe80")) { - emsesp::EMSESP::logger().info("IPv6 (%s) local: %s", link, ip6.c_str()); + emsesp::EMSESP::logger().debug("IPv6 (%s) local: %s", link, ip6.c_str()); } else if (ip6.startsWith("fd") || ip6.startsWith("fc")) { - emsesp::EMSESP::logger().info("IPv6 (%s) ULA: %s", link, ip6.c_str()); + emsesp::EMSESP::logger().debug("IPv6 (%s) ULA: %s", link, ip6.c_str()); } else { - emsesp::EMSESP::logger().info("IPv6 (%s) global: %s", link, ip6.c_str()); + emsesp::EMSESP::logger().debug("IPv6 (%s) global: %s", link, ip6.c_str()); } +#endif emsesp::EMSESP::system_.has_ipv6(true); } break; From ab67f97b401a677974701b6028cfeb528844e7c5 Mon Sep 17 00:00:00 2001 From: proddy Date: Mon, 27 Apr 2026 11:09:34 +0200 Subject: [PATCH 16/48] 3.8.2-dev.20 --- src/emsesp_version.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/emsesp_version.h b/src/emsesp_version.h index c98cd567e..e7b3ea13f 100644 --- a/src/emsesp_version.h +++ b/src/emsesp_version.h @@ -1 +1 @@ -#define EMSESP_APP_VERSION "3.8.2-dev.19" +#define EMSESP_APP_VERSION "3.8.2-dev.20" From 5e260f02395e740a3c9f2eed1f3d9e2fb3bbb668 Mon Sep 17 00:00:00 2001 From: proddy Date: Mon, 27 Apr 2026 11:09:51 +0200 Subject: [PATCH 17/48] refactoring --- src/web/WebStatusService.cpp | 69 +++++++++++++++++------------------- src/web/WebStatusService.h | 16 ++++++--- 2 files changed, 44 insertions(+), 41 deletions(-) diff --git a/src/web/WebStatusService.cpp b/src/web/WebStatusService.cpp index 5cc062804..31583585c 100644 --- a/src/web/WebStatusService.cpp +++ b/src/web/WebStatusService.cpp @@ -261,7 +261,7 @@ uint8_t WebStatusService::upgradeImportantMessages(std::string & version) { // it's a filename with a .bin or .md extension, try and extract the version from it // e.g. EMS-ESP-3_8_2-dev_13-ESP32-16MB+.bin -> major=3 minor=8 patch=2 - version::EMSESP_Version latest_version; + FirmwareVersion latest_version; if ((version.find(".bin") != std::string::npos) || (version.find(".md") != std::string::npos)) { std::string filename = version; auto pos = filename.find("EMS-ESP-"); @@ -282,18 +282,18 @@ uint8_t WebStatusService::upgradeImportantMessages(std::string & version) { std::string major_version = filename.substr(pos, underscore1 - pos); std::string minor_version = filename.substr(underscore1 + 1, underscore2 - underscore1 - 1); std::string patch_version = filename.substr(underscore2 + 1, dash - underscore2 - 1); - latest_version = version::EMSESP_Version(major_version + "." + minor_version + "." + patch_version); + latest_version = FirmwareVersion(major_version + "." + minor_version + "." + patch_version); } else { // if it's .json file exit if (version.find(".json") != std::string::npos) { return 0; } else { // treat it like a version string like "3.9.0" - latest_version = version::EMSESP_Version(version); + latest_version = FirmwareVersion(version); } } - version::EMSESP_Version current_version(current_version_s); // get current version + FirmwareVersion current_version(current_version_s); // get current version if ((current_version.major() <= 3 && current_version.minor() <= 8) && (latest_version.major() == 3 && latest_version.minor() == 9)) { return 1; // if moving from below 3.8.x to 3.9.x return 1 @@ -311,16 +311,13 @@ uint8_t WebStatusService::upgradeImportantMessages(std::string & version) { } // action = getVersions -// returns the device's current version info plus the cached "stable" and "dev" -// entries from emsesp.org/versions.json. The remote fetch is NOT done here: it -// runs from the main loop task via WebStatusService::loop() so we never block -// the AsyncTCP callback (which has a tiny ~6 KB stack — far too small for an -// HTTPS handshake). If we have no cached data yet (no internet, fetch still -// pending, parse error) the "stable" and "dev" sections are simply omitted so -// the client can detect the offline case. +// returns the device's current version for dev and stable +// The remote fetch runs from the main loop task via WebStatusService::loop() so that we never block the AsyncTCP callback void WebStatusService::getVersions(JsonObject root) { - version::EMSESP_Version current_version(current_version_s); - bool is_dev = current_version.prerelease().find("dev") != std::string::npos; + schedule_versions_refresh(); // force a refresh + + FirmwareVersion current_version(current_version_s); + bool is_dev = current_version.prerelease().find("dev") != std::string::npos; JsonObject current = root["current"].to(); current["version"] = current_version_s; @@ -347,8 +344,7 @@ void WebStatusService::getVersions(JsonObject root) { return; } - // copies a cached entry into root[key]. The upgradeable bool was computed - // once during refresh_versions_cache() so we just read it here. + // copies a cached entry into root[key] auto add_section = [&](const char * key, const VersionInfo & info) { if (info.version.empty()) { return; @@ -366,17 +362,17 @@ void WebStatusService::getVersions(JsonObject root) { JsonObject stable_out = root["stable"].to(); stable_out["version"] = "3.8.2"; stable_out["date"] = "2026-04-25"; - stable_out["upgradeable"] = version::EMSESP_Version("3.8.2") > current_version; + stable_out["upgradeable"] = FirmwareVersion("3.8.2") > current_version; JsonObject dev_out = root["dev"].to(); dev_out["version"] = "3.8.3-dev.2"; dev_out["date"] = "2026-04-25"; - dev_out["upgradeable"] = version::EMSESP_Version("3.8.3-dev.2") > current_version; + dev_out["upgradeable"] = FirmwareVersion("3.8.3-dev.2") > current_version; #endif } -// periodic refresh (1 hour) of the cached versions.json. Runs on the main loop task, -// which has a much bigger stack than AsyncTCP, so it's safe to do HTTPS here. +// periodic refresh (1 hour) of the cached versions.json +// runs on the main loop task, which has a much bigger stack than AsyncTCP needed for https void WebStatusService::loop() { #ifndef EMSESP_STANDALONE // need a network @@ -384,25 +380,17 @@ void WebStatusService::loop() { return; } - uint32_t now = uuid::get_uptime(); - - // first call after we have a network: schedule the initial fetch a little - // later so we give NTP / DNS a chance to settle + // 0 = idle, nothing scheduled if (versions_next_fetch_ms_ == 0) { - versions_next_fetch_ms_ = now + VERSIONS_INITIAL_DELAY_MS; - if (versions_next_fetch_ms_ == 0) { - versions_next_fetch_ms_ = 1; // avoid the "never scheduled" sentinel - } return; } // not time yet (signed difference handles uint32 wrap) - if ((int32_t)(now - versions_next_fetch_ms_) < 0) { + if ((int32_t)(uuid::get_uptime() - versions_next_fetch_ms_) < 0) { return; } - bool ok = refresh_versions_cache(); - + bool ok = refresh_versions_cache(); uint32_t next = uuid::get_uptime() + (ok ? VERSIONS_REFRESH_INTERVAL_MS : VERSIONS_RETRY_INTERVAL_MS); if (next == 0) { next = 1; @@ -421,14 +409,18 @@ bool WebStatusService::refresh_versions_cache() { http.setTimeout(5000); http.useHTTP10(true); - if (!http.begin("https://emsesp.org/versions.json")) { + if (!http.begin(EMSESP_VERSIONS_URL)) { +#if defined(EMSESP_DEBUG) EMSESP::logger().debug("versions.json: failed to start HTTPS request"); +#endif return false; } int httpCode = http.GET(); if (httpCode != HTTP_CODE_OK) { +#if defined(EMSESP_DEBUG) EMSESP::logger().debug("versions.json: HTTP %d", httpCode); +#endif http.end(); return false; } @@ -437,11 +429,13 @@ bool WebStatusService::refresh_versions_cache() { DeserializationError err = deserializeJson(doc, http.getStream()); http.end(); if (err) { +#if defined(EMSESP_DEBUG) EMSESP::logger().debug("versions.json: parse error (%s)", err.c_str()); +#endif return false; } - version::EMSESP_Version current_version(current_version_s); + FirmwareVersion current_version(current_version_s); auto read_section = [&doc, ¤t_version](const char * key, VersionInfo & out) { JsonObjectConst section = doc[key]; @@ -451,7 +445,7 @@ bool WebStatusService::refresh_versions_cache() { } out.version = section["version"] | ""; out.date = section["date"] | ""; - out.upgradeable = !out.version.empty() && version::EMSESP_Version(out.version) > current_version; + out.upgradeable = !out.version.empty() && FirmwareVersion(out.version) > current_version; }; read_section("stable", versions_stable_); @@ -459,7 +453,10 @@ bool WebStatusService::refresh_versions_cache() { versions_cache_valid_ = true; #if defined(EMSESP_DEBUG) - EMSESP::logger().debug("versions.json refreshed (stable=%s dev=%s)", versions_stable_.version.c_str(), versions_dev_.version.c_str()); + EMSESP::logger().debug("versions.json: refreshed (stable=%s dev=%s), current=%s", + versions_stable_.version.c_str(), + versions_dev_.version.c_str(), + current_version_s.c_str()); #endif return true; #endif @@ -470,8 +467,8 @@ bool WebStatusService::current_upgradeable() const { if (!versions_cache_valid_) { return false; } - version::EMSESP_Version current_version(current_version_s); - bool is_dev = current_version.prerelease().find("dev") != std::string::npos; + FirmwareVersion current_version(current_version_s); + bool is_dev = current_version.prerelease().find("dev") != std::string::npos; return is_dev ? versions_dev_.upgradeable : versions_stable_.upgradeable; } diff --git a/src/web/WebStatusService.h b/src/web/WebStatusService.h index 706d5d956..90414b6b8 100644 --- a/src/web/WebStatusService.h +++ b/src/web/WebStatusService.h @@ -4,7 +4,9 @@ #define EMSESP_SYSTEM_STATUS_SERVICE_PATH "/rest/systemStatus" #define EMSESP_ACTION_SERVICE_PATH "/rest/action" -#include "../core/EMSESP_Version.h" +#define EMSESP_VERSIONS_URL "http://emsesp.org/versions.json" + +#include "../core/firmwareVersion.h" #include "../emsesp_version.h" namespace emsesp { @@ -19,8 +21,8 @@ class WebStatusService { return current_version_s; } - // called from EMSESP::loop() to refresh the cached versions.json from emsesp.org so that the web - // request handler never has to do blocking HTTPS on the small AsyncTCP stack + // called from EMSESP::loop() to refresh the cached versions.json from emsesp.org + // so that the web request handler never has to do blocking HTTPS on the small AsyncTCP stack void loop(); // true once we've had at least one successful versions.json fetch @@ -28,6 +30,11 @@ class WebStatusService { return versions_cache_valid_; } + // refresh the versions.json cache + void schedule_versions_refresh() { + versions_next_fetch_ms_ = 1; + } + bool current_upgradeable() const; // true if a newer version is available // make action function public so we can test in the debug and standalone mode @@ -60,13 +67,12 @@ class WebStatusService { VersionInfo versions_stable_; VersionInfo versions_dev_; bool versions_cache_valid_ = false; // true once we've had at least one successful fetch - uint32_t versions_next_fetch_ms_ = 0; // uuid::get_uptime() of the next attempt; 0 = ASAP + uint32_t versions_next_fetch_ms_ = 0; // uuid::get_uptime() of the next attempt; 0 = idle bool refresh_versions_cache(); // does the actual HTTPS fetch + parse, returns true on success static constexpr uint32_t VERSIONS_REFRESH_INTERVAL_MS = 60UL * 60UL * 1000UL; // 1 hour on success static constexpr uint32_t VERSIONS_RETRY_INTERVAL_MS = 5UL * 60UL * 1000UL; // 5 min after failure - static constexpr uint32_t VERSIONS_INITIAL_DELAY_MS = 30UL * 1000UL; // wait 30s after boot }; } // namespace emsesp From 43ec5c19253640ae886cdfb3ec48ca40004b30e5 Mon Sep 17 00:00:00 2001 From: proddy Date: Mon, 27 Apr 2026 11:30:40 +0200 Subject: [PATCH 18/48] move mockserver to standalone section only --- interface/vite.config.ts | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/interface/vite.config.ts b/interface/vite.config.ts index 8bea7e00d..e04cdde27 100644 --- a/interface/vite.config.ts +++ b/interface/vite.config.ts @@ -6,9 +6,6 @@ import { Plugin, PluginOption, defineConfig } from 'vite'; import viteImagemin from 'vite-plugin-imagemin'; import zlib from 'zlib'; -// @ts-expect-error - mock server doesn't have type declarations -import mockServer from '../mock-api/mockServer.js'; - // Constants const KB_DIVISOR = 1024; const REPEAT_CHAR = '='; @@ -100,6 +97,10 @@ const createPreactPlugin = (devToolsEnabled: boolean) => // Patch preact/compat to export stub React 19 APIs (use, useOptimistic) so that // react-router v7 doesn't trigger IMPORT_IS_UNDEFINED warnings from Rolldown. +// Rolldown tracks the constant strings used in `React[REACT_USE]` / +// `React[USE_OPTIMISTIC]` lookups inside react-router and resolves them +// statically, so simply relying on a runtime guard is not enough — we need +// matching (stub) exports on the aliased preact/compat module. const preactCompatPatchPlugin = (): Plugin => ({ name: 'preact-compat-react19-patch', transform(code, id) { @@ -210,9 +211,11 @@ const imageOptimizationPlugin = { }; export default defineConfig( - ({ command, mode }: { command: string; mode: string }) => { + async ({ command, mode }: { command: string; mode: string }) => { if (command === 'serve') { console.log(`Preparing for standalone build with server, mode=${mode}`); + // @ts-expect-error - mock server doesn't have type declarations + const { default: mockServer } = await import('../mock-api/mockServer.js'); return { plugins: [...createBasePlugins(true, true), mockServer()], resolve: { From c5b262af8a0b6b9fe16451575a8690b5a8e6ef11 Mon Sep 17 00:00:00 2001 From: proddy Date: Mon, 27 Apr 2026 11:34:03 +0200 Subject: [PATCH 19/48] dont update cloudflare KV for forks --- .github/workflows/dev_release.yml | 1 + .github/workflows/stable_release.yml | 1 + 2 files changed, 2 insertions(+) diff --git a/.github/workflows/dev_release.yml b/.github/workflows/dev_release.yml index 97e4f5ffc..bb97b1863 100644 --- a/.github/workflows/dev_release.yml +++ b/.github/workflows/dev_release.yml @@ -79,6 +79,7 @@ jobs: ./build/firmware/*.* - name: Update version in Cloudflare KV store + if: github.repository == 'emsesp/EMS-ESP32' env: CF_ACCOUNT_ID: ${{ secrets.CF_ACCOUNT_ID }} CF_NAMESPACE_ID: ${{ secrets.CF_NAMESPACE_ID }} diff --git a/.github/workflows/stable_release.yml b/.github/workflows/stable_release.yml index a5fe7b7ae..93f45ad3d 100644 --- a/.github/workflows/stable_release.yml +++ b/.github/workflows/stable_release.yml @@ -70,6 +70,7 @@ jobs: ./build/firmware/*.* - name: Update version in Cloudflare KV store + if: github.repository == 'emsesp/EMS-ESP32' env: CF_ACCOUNT_ID: ${{ secrets.CF_ACCOUNT_ID }} CF_NAMESPACE_ID: ${{ secrets.CF_NAMESPACE_ID }} From e39af36589e17d77200a47e502bd0469153cd0d3 Mon Sep 17 00:00:00 2001 From: proddy Date: Mon, 27 Apr 2026 13:23:39 +0200 Subject: [PATCH 20/48] fix lint errors --- interface/src/app/settings/security/SecuritySettings.tsx | 2 +- interface/src/components/upload/DragNdrop.tsx | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/interface/src/app/settings/security/SecuritySettings.tsx b/interface/src/app/settings/security/SecuritySettings.tsx index 117a84467..40cac3de6 100644 --- a/interface/src/app/settings/security/SecuritySettings.tsx +++ b/interface/src/app/settings/security/SecuritySettings.tsx @@ -79,7 +79,7 @@ const SecuritySettings = () => { onChange={updateFormValue} margin="normal" /> - + {dirtyFlags && dirtyFlags.length !== 0 && ( @@ -214,7 +190,6 @@ const HelpComponent = () => { ); }; -// Memoize the component to prevent unnecessary re-renders const Help = memo(HelpComponent); export default Help; diff --git a/interface/src/app/main/Modules.tsx b/interface/src/app/main/Modules.tsx index 91d126f5a..82ea16079 100644 --- a/interface/src/app/main/Modules.tsx +++ b/interface/src/app/main/Modules.tsx @@ -1,4 +1,4 @@ -import { memo, useCallback, useMemo, useState } from 'react'; +import { memo, useState } from 'react'; import { useBlocker } from 'react-router'; import { toast } from 'react-toastify'; @@ -69,58 +69,53 @@ const Modules = () => { } ); - const modules_theme = useTheme( - useMemo( - () => ({ - Table: ` - --data-table-library_grid-template-columns: 48px 180px 120px 100px repeat(1, minmax(160px, 1fr)) 180px; - `, - BaseRow: ` - font-size: 14px; - .td { - height: 32px; - } - `, - BaseCell: ` - &:nth-of-type(1) { - text-align: center; - } - `, - HeaderRow: ` - text-transform: uppercase; - background-color: black; - color: #90CAF9; - .th { - border-bottom: 1px solid #565656; - height: 36px; - } - `, - Row: ` - background-color: #1e1e1e; - position: relative; - cursor: pointer; - .td { - border-top: 1px solid #565656; - border-bottom: 1px solid #565656; - } - &:hover .td { - border-top: 1px solid #177ac9; - border-bottom: 1px solid #177ac9; - } - &:nth-of-type(odd) .td { - background-color: #303030; - } - ` - }), - [] - ) - ); + const modules_theme = useTheme({ + Table: ` + --data-table-library_grid-template-columns: 48px 180px 120px 100px repeat(1, minmax(160px, 1fr)) 180px; + `, + BaseRow: ` + font-size: 14px; + .td { + height: 32px; + } + `, + BaseCell: ` + &:nth-of-type(1) { + text-align: center; + } + `, + HeaderRow: ` + text-transform: uppercase; + background-color: black; + color: #90CAF9; + .th { + border-bottom: 1px solid #565656; + height: 36px; + } + `, + Row: ` + background-color: #1e1e1e; + position: relative; + cursor: pointer; + .td { + border-top: 1px solid #565656; + border-bottom: 1px solid #565656; + } + &:hover .td { + border-top: 1px solid #177ac9; + border-bottom: 1px solid #177ac9; + } + &:nth-of-type(odd) .td { + background-color: #303030; + } + ` + }); - const onDialogClose = useCallback(() => { + const onDialogClose = () => { setDialogOpen(false); - }, []); + }; - const updateModuleItem = useCallback((updatedItem: ModuleItem) => { + const updateModuleItem = (updatedItem: ModuleItem) => { void updateState(readModules(), (data: ModuleItem[]) => { const new_data = data.map((mi) => mi.id === updatedItem.id ? { ...mi, ...updatedItem } : mi @@ -128,28 +123,25 @@ const Modules = () => { setNumChanges(new_data.filter(hasModulesChanged).length); return new_data; }); - }, []); + }; - const onDialogSave = useCallback( - (updatedItem: ModuleItem) => { - setDialogOpen(false); - updateModuleItem(updatedItem); - }, - [updateModuleItem] - ); + const onDialogSave = (updatedItem: ModuleItem) => { + setDialogOpen(false); + updateModuleItem(updatedItem); + }; - const editModuleItem = useCallback((mi: ModuleItem) => { + const editModuleItem = (mi: ModuleItem) => { setSelectedModuleItem(mi); setDialogOpen(true); - }, []); + }; - const onCancel = useCallback(async () => { + const onCancel = async () => { await fetchModules().then(() => { setNumChanges(0); }); - }, [fetchModules]); + }; - const saveModules = useCallback(async () => { + const saveModules = async () => { try { await Promise.all( modules.map((condensed_mi: ModuleItem) => @@ -167,9 +159,9 @@ const Modules = () => { await fetchModules(); setNumChanges(0); } - }, [modules, updateModules, LL, fetchModules]); + }; - const content = useMemo(() => { + const renderContent = () => { if (!modules) { return ( @@ -262,22 +254,12 @@ const Modules = () => { ); - }, [ - modules, - fetchModules, - error, - modules_theme, - editModuleItem, - LL, - numChanges, - onCancel, - saveModules - ]); + }; return ( {blocker ? : null} - {content} + {renderContent()} {selectedModuleItem && ( (selectedItem); - const updateFormValue = useMemo( - () => - updateValue( - setEditItem as unknown as React.Dispatch< - React.SetStateAction> - > - ), - [] + const updateFormValue = updateValue( + setEditItem as unknown as React.Dispatch< + React.SetStateAction> + > ); // Sync form state when dialog opens or selected item changes @@ -54,18 +50,13 @@ const ModulesDialog = ({ } }, [open, selectedItem]); - const handleSave = useCallback(() => { + const handleSave = () => { onSave(editItem); - }, [editItem, onSave]); - - const dialogTitle = useMemo( - () => `${LL.EDIT()} ${editItem.key}`, - [LL, editItem.key] - ); + }; return ( - {dialogTitle} + {`${LL.EDIT()} ${editItem.key}`} { } ); - const hasScheduleChanged = useCallback((si: ScheduleItem) => { + const hasScheduleChanged = (si: ScheduleItem) => { return ( si.id !== si.o_id || (si.name || '') !== (si.o_name || '') || @@ -143,15 +143,13 @@ const Scheduler = () => { si.cmd !== si.o_cmd || si.value !== si.o_value ); - }, []); + }; - const intervalCallback = useCallback(() => { + useInterval(() => { if (numChanges === 0) { void fetchSchedule(); } - }, [numChanges, fetchSchedule]); - - useInterval(intervalCallback, INTERVAL_DELAY); + }, INTERVAL_DELAY); useEffect(() => { const formatter = new Intl.DateTimeFormat(locale, { @@ -169,7 +167,7 @@ const Scheduler = () => { const schedule_theme = useTheme(scheduleTheme); - const saveSchedule = useCallback(async () => { + const saveSchedule = async () => { try { await updateSchedule({ schedule: schedule @@ -192,46 +190,43 @@ const Scheduler = () => { await fetchSchedule(); setNumChanges(0); } - }, [LL, schedule, updateSchedule, fetchSchedule]); + }; - const editScheduleItem = useCallback((si: ScheduleItem) => { + const editScheduleItem = (si: ScheduleItem) => { setCreating(false); setSelectedScheduleItem(si); setDialogOpen(true); if (si.o_name === undefined) { si.o_name = si.name; } - }, []); + }; - const onDialogClose = useCallback(() => { + const onDialogClose = () => { setDialogOpen(false); - }, []); + }; - const onDialogCancel = useCallback(async () => { + const onDialogCancel = async () => { await fetchSchedule().then(() => { setNumChanges(0); }); - }, [fetchSchedule]); + }; - const onDialogSave = useCallback( - (updatedItem: ScheduleItem) => { - setDialogOpen(false); - void updateState(readSchedule(), (data: ScheduleItem[]) => { - const new_data = creating - ? [...data, updatedItem] - : data.map((si) => - si.id === updatedItem.id ? { ...si, ...updatedItem } : si - ); + const onDialogSave = (updatedItem: ScheduleItem) => { + setDialogOpen(false); + void updateState(readSchedule(), (data: ScheduleItem[]) => { + const new_data = creating + ? [...data, updatedItem] + : data.map((si) => + si.id === updatedItem.id ? { ...si, ...updatedItem } : si + ); - setNumChanges(new_data.filter((si) => hasScheduleChanged(si)).length); + setNumChanges(new_data.filter((si) => hasScheduleChanged(si)).length); - return new_data; - }); - }, - [creating, hasScheduleChanged] - ); + return new_data; + }); + }; - const addScheduleItem = useCallback(() => { + const addScheduleItem = () => { setCreating(true); const newItem: ScheduleItem = { id: Math.floor(Math.random() * (MAX_ID - MIN_ID) + MIN_ID), @@ -239,36 +234,29 @@ const Scheduler = () => { }; setSelectedScheduleItem(newItem); setDialogOpen(true); - }, []); + }; - const filteredAndSortedSchedule = useMemo( - () => - schedule - .filter((si: ScheduleItem) => !si.deleted) - .sort((a: ScheduleItem, b: ScheduleItem) => a.flags - b.flags), - [schedule] - ); + const filteredAndSortedSchedule = schedule + .filter((si: ScheduleItem) => !si.deleted) + .sort((a: ScheduleItem, b: ScheduleItem) => a.flags - b.flags); - const dayBox = useCallback( - (si: ScheduleItem, flag: number) => { - const dayIndex = Math.log(flag) / LOG_2; - const isActive = (si.flags & flag) === flag; + const dayBox = (si: ScheduleItem, flag: number) => { + const dayIndex = Math.log(flag) / LOG_2; + const isActive = (si.flags & flag) === flag; - return ( - <> - - - {dow[dayIndex]} - - - - - ); - }, - [dow] - ); + return ( + <> + + + {dow[dayIndex]} + + + + + ); + }; - const scheduleType = useCallback((si: ScheduleItem) => { + const scheduleType = (si: ScheduleItem) => { const label = scheduleTypeLabels[si.flags]; return ( @@ -278,9 +266,9 @@ const Scheduler = () => { ); - }, []); + }; - const renderSchedule = useCallback(() => { + const renderSchedule = () => { if (!schedule) { return ( @@ -343,17 +331,7 @@ const Scheduler = () => { )} ); - }, [ - schedule, - error, - fetchSchedule, - filteredAndSortedSchedule, - schedule_theme, - editScheduleItem, - LL, - dayBox, - scheduleType - ]); + }; return ( diff --git a/interface/src/app/main/SchedulerDialog.tsx b/interface/src/app/main/SchedulerDialog.tsx index 27713de86..ca3ff97dd 100644 --- a/interface/src/app/main/SchedulerDialog.tsx +++ b/interface/src/app/main/SchedulerDialog.tsx @@ -1,4 +1,4 @@ -import { useCallback, useEffect, useMemo, useState } from 'react'; +import { useEffect, useMemo, useState } from 'react'; import AddIcon from '@mui/icons-material/Add'; import CancelIcon from '@mui/icons-material/Cancel'; @@ -60,6 +60,12 @@ const FLAG_VALUES = [ ScheduleFlag.SCHEDULE_SAT ] as const; +const getFlagDOWnumber = (flags: string[]) => + flags.reduce((acc, flag) => acc | Number(flag), 0) & FLAG_MASK_127; + +const getFlagDOWstring = (f: number) => + FLAG_VALUES.filter((flag) => (f & flag) === flag).map((flag) => String(flag)); + interface SchedulerDialogProps { open: boolean; creating: boolean; @@ -84,6 +90,7 @@ const SchedulerDialog = ({ const [fieldErrors, setFieldErrors] = useState(); const [scheduleType, setScheduleType] = useState(); + // Stable handler reference so the memoized ValidatedTextField can skip re-renders const updateFormValue = useMemo( () => updateValue( @@ -112,129 +119,95 @@ const SchedulerDialog = ({ } }, [open, selectedItem]); - // Helper function to handle save operations - const handleSave = useCallback( - async (itemToSave: ScheduleItem) => { - try { - setFieldErrors(undefined); - await validate(validator, itemToSave); - onSave(itemToSave); - } catch (error) { - setFieldErrors((error as ValidationError).fieldErrors); - } - }, - [validator, onSave] - ); - - const save = useCallback(async () => { - await handleSave(editItem); - }, [editItem, handleSave]); - - const saveandactivate = useCallback(async () => { - await handleSave({ ...editItem, active: true }); - }, [editItem, handleSave]); - - const remove = useCallback(() => { - onSave({ ...editItem, deleted: true }); - }, [editItem, onSave]); - - // Optimize DOW flag conversion - const getFlagDOWnumber = useCallback((flags: string[]) => { - return flags.reduce((acc, flag) => acc | Number(flag), 0) & FLAG_MASK_127; - }, []); - - const getFlagDOWstring = useCallback((f: number) => { - return FLAG_VALUES.filter((flag) => (f & flag) === flag).map((flag) => - String(flag) - ); - }, []); - - // Day of week display component - const DayOfWeekButton = useCallback( - (flag: number) => { - const dayIndex = Math.log2(flag); - const isSelected = (editItem.flags & flag) === flag; - return ( - - {dow[dayIndex]} - - ); - }, - [editItem.flags, dow] - ); - - const handleClose = useCallback( - (_event: React.SyntheticEvent, reason: 'backdropClick' | 'escapeKeyDown') => { - if (reason !== 'backdropClick') { - onClose(); - } - }, - [onClose] - ); - - const handleScheduleTypeChange = useCallback( - (_event: React.SyntheticEvent, flag: ScheduleFlag | null) => { - if (flag !== null) { - setFieldErrors(undefined); // clear any validation errors - setScheduleType(flag); - // wipe the time field when changing the schedule type - // set the flags based on type - const newFlags = flag === ScheduleFlag.SCHEDULE_DAY ? FLAG_ALL_DAYS : flag; - setEditItem((prev) => ({ ...prev, time: '', flags: newFlags })); - } - }, - [] - ); - - const handleDOWChange = useCallback( - (_event: React.SyntheticEvent, flags: string[]) => { - const newFlags = - getFlagDOWnumber(flags) === 0 ? FLAG_ALL_DAYS : getFlagDOWnumber(flags); - setEditItem((prev) => ({ ...prev, flags: newFlags })); - }, - [getFlagDOWnumber] - ); - - // Memoize derived values - const isDaySchedule = useMemo( - () => scheduleType === ScheduleFlag.SCHEDULE_DAY, - [scheduleType] - ); - const isTimerSchedule = useMemo( - () => scheduleType === ScheduleFlag.SCHEDULE_TIMER, - [scheduleType] - ); - const isImmediateSchedule = useMemo( - () => scheduleType === ScheduleFlag.SCHEDULE_IMMEDIATE, - [scheduleType] - ); - const needsTimeField = useMemo( - () => isDaySchedule || isTimerSchedule, - [isDaySchedule, isTimerSchedule] - ); - - const dowFlags = useMemo( - () => getFlagDOWstring(editItem.flags), - [editItem.flags, getFlagDOWstring] - ); - - const timeFieldValue = useMemo(() => { - if (needsTimeField) { - return editItem.time === '' ? DEFAULT_TIME : editItem.time; + const handleSave = async (itemToSave: ScheduleItem) => { + try { + setFieldErrors(undefined); + await validate(validator, itemToSave); + onSave(itemToSave); + } catch (error) { + setFieldErrors((error as ValidationError).fieldErrors); } - return editItem.time === DEFAULT_TIME ? '' : editItem.time; - }, [editItem.time, needsTimeField]); + }; - const timeFieldLabel = useMemo(() => { + const save = async () => { + await handleSave(editItem); + }; + + const saveandactivate = async () => { + await handleSave({ ...editItem, active: true }); + }; + + const remove = () => { + onSave({ ...editItem, deleted: true }); + }; + + const DayOfWeekButton = (flag: number) => { + const dayIndex = Math.log2(flag); + const isSelected = (editItem.flags & flag) === flag; + return ( + + {dow[dayIndex]} + + ); + }; + + const handleClose = ( + _event: React.SyntheticEvent, + reason: 'backdropClick' | 'escapeKeyDown' + ) => { + if (reason !== 'backdropClick') { + onClose(); + } + }; + + const handleScheduleTypeChange = ( + _event: React.SyntheticEvent, + flag: ScheduleFlag | null + ) => { + if (flag !== null) { + setFieldErrors(undefined); // clear any validation errors + setScheduleType(flag); + // wipe the time field when changing the schedule type + // set the flags based on type + const newFlags = flag === ScheduleFlag.SCHEDULE_DAY ? FLAG_ALL_DAYS : flag; + setEditItem((prev) => ({ ...prev, time: '', flags: newFlags })); + } + }; + + const handleDOWChange = ( + _event: React.SyntheticEvent, + flags: string[] + ) => { + const newFlags = + getFlagDOWnumber(flags) === 0 ? FLAG_ALL_DAYS : getFlagDOWnumber(flags); + setEditItem((prev) => ({ ...prev, flags: newFlags })); + }; + + const isDaySchedule = scheduleType === ScheduleFlag.SCHEDULE_DAY; + const isTimerSchedule = scheduleType === ScheduleFlag.SCHEDULE_TIMER; + const isImmediateSchedule = scheduleType === ScheduleFlag.SCHEDULE_IMMEDIATE; + const needsTimeField = isDaySchedule || isTimerSchedule; + + const dowFlags = getFlagDOWstring(editItem.flags); + + const timeFieldValue = needsTimeField + ? editItem.time === '' + ? DEFAULT_TIME + : editItem.time + : editItem.time === DEFAULT_TIME + ? '' + : editItem.time; + + const timeFieldLabel = (() => { if (scheduleType === ScheduleFlag.SCHEDULE_TIMER) return LL.TIMER(1); if (scheduleType === ScheduleFlag.SCHEDULE_CONDITION) return LL.CONDITION(); if (scheduleType === ScheduleFlag.SCHEDULE_ONCHANGE) return LL.ONCHANGE(); if (scheduleType === ScheduleFlag.SCHEDULE_IMMEDIATE) return LL.IMMEDIATE(); return LL.TIME(1); - }, [scheduleType, LL]); + })(); return ( diff --git a/interface/src/app/main/Sensors.tsx b/interface/src/app/main/Sensors.tsx index 028c362fc..b19a5c146 100644 --- a/interface/src/app/main/Sensors.tsx +++ b/interface/src/app/main/Sensors.tsx @@ -1,4 +1,4 @@ -import { useCallback, useContext, useMemo, useRef, useState } from 'react'; +import { useContext, useRef, useState } from 'react'; import { toast } from 'react-toastify'; import AddCircleOutlineOutlinedIcon from '@mui/icons-material/AddCircleOutlineOutlined'; @@ -158,18 +158,16 @@ const Sensors = () => { } ); - const intervalCallback = useCallback(() => { + useInterval(() => { if (!temperatureDialogOpen && !analogDialogOpen) { void fetchSensorData(); } - }, [temperatureDialogOpen, analogDialogOpen, fetchSensorData]); - - useInterval(intervalCallback); + }); const temperature_theme = useTheme([common_theme, temperature_theme_config]); const analog_theme = useTheme([common_theme, analog_theme_config]); - const getSortIcon = useCallback((state: State, sortKey: unknown) => { + const getSortIcon = (state: State, sortKey: unknown) => { if (state.sortKey === sortKey && state.reverse) { return ; } @@ -177,7 +175,7 @@ const Sensors = () => { return ; } return ; - }, []); + }; const analog_sort = useSort( { nodes: sensorData.as }, @@ -234,119 +232,104 @@ const Sensors = () => { useLayoutTitle(LL.SENSORS()); - const formatDurationMin = useCallback( - (duration_min: number) => { - const totalMs = duration_min * MS_PER_MINUTE; - const days = Math.trunc(totalMs / MS_PER_DAY); - const hours = Math.trunc(totalMs / MS_PER_HOUR) % 24; - const minutes = Math.trunc(totalMs / MS_PER_MINUTE) % 60; + const formatDurationMin = (duration_min: number) => { + const totalMs = duration_min * MS_PER_MINUTE; + const days = Math.trunc(totalMs / MS_PER_DAY); + const hours = Math.trunc(totalMs / MS_PER_HOUR) % 24; + const minutes = Math.trunc(totalMs / MS_PER_MINUTE) % 60; - const parts: string[] = []; - if (days > 0) { - parts.push(LL.NUM_DAYS({ num: days })); - } - if (hours > 0) { - parts.push(LL.NUM_HOURS({ num: hours })); - } - if (minutes > 0) { - parts.push(LL.NUM_MINUTES({ num: minutes })); - } - return parts.join(' '); - }, - [LL] - ); + const parts: string[] = []; + if (days > 0) { + parts.push(LL.NUM_DAYS({ num: days })); + } + if (hours > 0) { + parts.push(LL.NUM_HOURS({ num: hours })); + } + if (minutes > 0) { + parts.push(LL.NUM_MINUTES({ num: minutes })); + } + return parts.join(' '); + }; - const formatValue = useCallback( - (value: unknown, uom: DeviceValueUOM) => { - if (value === undefined) { - return ''; - } - if (typeof value !== 'number') { - return value as string; - } - switch (uom) { - case DeviceValueUOM.HOURS: - return value ? formatDurationMin(value * 60) : LL.NUM_HOURS({ num: 0 }); - case DeviceValueUOM.MINUTES: - return value ? formatDurationMin(value) : LL.NUM_MINUTES({ num: 0 }); - case DeviceValueUOM.SECONDS: - return LL.NUM_SECONDS({ num: value }); - case DeviceValueUOM.NONE: - return new Intl.NumberFormat().format(value); - case DeviceValueUOM.DEGREES: - case DeviceValueUOM.DEGREES_R: - case DeviceValueUOM.FAHRENHEIT: - return ( - new Intl.NumberFormat(undefined, { - minimumFractionDigits: 1 - }).format(value) + - ' ' + - DeviceValueUOM_s[uom] - ); - default: - return new Intl.NumberFormat().format(value) + ' ' + DeviceValueUOM_s[uom]; - } - }, - [formatDurationMin, LL] - ); + const formatValue = (value: unknown, uom: DeviceValueUOM) => { + if (value === undefined) { + return ''; + } + if (typeof value !== 'number') { + return value as string; + } + switch (uom) { + case DeviceValueUOM.HOURS: + return value ? formatDurationMin(value * 60) : LL.NUM_HOURS({ num: 0 }); + case DeviceValueUOM.MINUTES: + return value ? formatDurationMin(value) : LL.NUM_MINUTES({ num: 0 }); + case DeviceValueUOM.SECONDS: + return LL.NUM_SECONDS({ num: value }); + case DeviceValueUOM.NONE: + return new Intl.NumberFormat().format(value); + case DeviceValueUOM.DEGREES: + case DeviceValueUOM.DEGREES_R: + case DeviceValueUOM.FAHRENHEIT: + return ( + new Intl.NumberFormat(undefined, { + minimumFractionDigits: 1 + }).format(value) + + ' ' + + DeviceValueUOM_s[uom] + ); + default: + return new Intl.NumberFormat().format(value) + ' ' + DeviceValueUOM_s[uom]; + } + }; - const updateTemperatureSensor = useCallback( - (ts: TemperatureSensor) => { - if (me.admin) { - ts.o_n = ts.n; - setSelectedTemperatureSensor(ts); - setTemperatureDialogOpen(true); - } - }, - [me.admin] - ); + const updateTemperatureSensor = (ts: TemperatureSensor) => { + if (me.admin) { + ts.o_n = ts.n; + setSelectedTemperatureSensor(ts); + setTemperatureDialogOpen(true); + } + }; - const onTemperatureDialogClose = useCallback(() => { + const onTemperatureDialogClose = () => { setTemperatureDialogOpen(false); void fetchSensorData(); - }, [fetchSensorData]); + }; - const onTemperatureDialogSave = useCallback( - async (ts: TemperatureSensor) => { - await sendTemperatureSensor({ - id: ts.id, - name: ts.n, - offset: ts.o, - is_system: ts.s + const onTemperatureDialogSave = async (ts: TemperatureSensor) => { + await sendTemperatureSensor({ + id: ts.id, + name: ts.n, + offset: ts.o, + is_system: ts.s + }) + .then(() => { + toast.success(LL.UPDATED_OF(LL.SENSOR(1))); }) - .then(() => { - toast.success(LL.UPDATED_OF(LL.SENSOR(1))); - }) - .catch(() => { - toast.error(LL.UPDATE_OF(LL.SENSOR(2)) + ' ' + LL.FAILED(1)); - }) - .finally(() => { - setTemperatureDialogOpen(false); - setSelectedTemperatureSensor(undefined); - void fetchSensorData(); - }); - }, - [sendTemperatureSensor, LL, fetchSensorData] - ); + .catch(() => { + toast.error(LL.UPDATE_OF(LL.SENSOR(2)) + ' ' + LL.FAILED(1)); + }) + .finally(() => { + setTemperatureDialogOpen(false); + setSelectedTemperatureSensor(undefined); + void fetchSensorData(); + }); + }; - const updateAnalogSensor = useCallback( - (as: AnalogSensor) => { - if (me.admin) { - setCreating(false); - as.o_n = as.n; - setSelectedAnalogSensor(as); - setAnalogDialogOpen(true); - } - }, - [me.admin] - ); + const updateAnalogSensor = (as: AnalogSensor) => { + if (me.admin) { + setCreating(false); + as.o_n = as.n; + setSelectedAnalogSensor(as); + setAnalogDialogOpen(true); + } + }; - const onAnalogDialogClose = useCallback(() => { + const onAnalogDialogClose = () => { setAnalogDialogOpen(false); void fetchSensorData(); - }, [fetchSensorData]); + }; - const addAnalogSensor = useCallback(() => { + const addAnalogSensor = () => { if (firstAvailableGPIO.current === undefined) { toast.error(LL.NO_GPIO()); return; @@ -366,194 +349,167 @@ const Sensors = () => { o_n: '' }); setAnalogDialogOpen(true); - }, []); + }; - const onAnalogDialogSave = useCallback( - async (as: AnalogSensor) => { - await sendAnalogSensor({ - id: as.id, - gpio: as.g, - name: as.n, - offset: as.o, - factor: as.f, - uom: as.u, - type: as.t, - deleted: as.d, - is_system: as.s + const onAnalogDialogSave = async (as: AnalogSensor) => { + await sendAnalogSensor({ + id: as.id, + gpio: as.g, + name: as.n, + offset: as.o, + factor: as.f, + uom: as.u, + type: as.t, + deleted: as.d, + is_system: as.s + }) + .then(() => { + toast.success(LL.UPDATED_OF(LL.ANALOG_SENSOR(2))); }) - .then(() => { - toast.success(LL.UPDATED_OF(LL.ANALOG_SENSOR(2))); - }) - .catch(() => { - toast.error(LL.UPDATE_OF(LL.ANALOG_SENSOR(5)) + ' ' + LL.FAILED(1)); - }) - .finally(() => { - setAnalogDialogOpen(false); - setSelectedAnalogSensor(undefined); - void fetchSensorData(); - }); - }, - [sendAnalogSensor, LL, fetchSensorData] + .catch(() => { + toast.error(LL.UPDATE_OF(LL.ANALOG_SENSOR(5)) + ' ' + LL.FAILED(1)); + }) + .finally(() => { + setAnalogDialogOpen(false); + setSelectedAnalogSensor(undefined); + void fetchSensorData(); + }); + }; + + const RenderAnalogSensors = ( + + {(tableList: AnalogSensor[]) => ( + <> +
+ + + + + + + + + + + + + + +
+ + {tableList.map((as: AnalogSensor) => ( + updateAnalogSensor(as)} + > + {as.g} + {as.n} + {AnalogTypeNames[as.t - 1]} + {(as.t === AnalogType.DIGITAL_OUT && + as.g !== GPIO_25 && + as.g !== GPIO_26) || + as.t === AnalogType.DIGITAL_IN || + as.t === AnalogType.PULSE ? ( + {as.v ? LL.ON() : LL.OFF()} + ) : ( + {formatValue(as.v, as.u)} + )} + + ))} + + + )} +
); - const RenderAnalogSensors = useMemo( - () => ( - - {(tableList: AnalogSensor[]) => ( - <> -
- - - - - - - - - - - - - - -
- - {tableList.map((as: AnalogSensor) => ( - updateAnalogSensor(as)} + const RenderTemperatureSensors = ( +
+ {(tableList: TemperatureSensor[]) => ( + <> +
+ + +
- ), - [ - analog_sort, - analog_theme, - getSortIcon, - sensorData.as, - LL, - updateAnalogSensor, - formatValue - ] - ); - - const RenderTemperatureSensors = useMemo( - () => ( - - {(tableList: TemperatureSensor[]) => ( - <> -
- - - - - - - - -
- - {tableList.map((ts: TemperatureSensor) => ( - updateTemperatureSensor(ts)} + {LL.NAME(0)} + + + +
- ), - [ - temperature_sort, - temperature_theme, - getSortIcon, - sensorData.ts, - LL, - updateTemperatureSensor, - formatValue - ] + {LL.VALUE(0)} + + + + + + {tableList.map((ts: TemperatureSensor) => ( + updateTemperatureSensor(ts)} + > + {ts.n} + {formatValue(ts.t, ts.u)} + + ))} + + + )} + ); return ( diff --git a/interface/src/app/main/SensorsAnalogDialog.tsx b/interface/src/app/main/SensorsAnalogDialog.tsx index ffc5a4485..283a00803 100644 --- a/interface/src/app/main/SensorsAnalogDialog.tsx +++ b/interface/src/app/main/SensorsAnalogDialog.tsx @@ -1,4 +1,4 @@ -import { useCallback, useEffect, useMemo, useState } from 'react'; +import { useEffect, useMemo, useState } from 'react'; import CancelIcon from '@mui/icons-material/Cancel'; import DoneIcon from '@mui/icons-material/Done'; @@ -53,6 +53,7 @@ const SensorsAnalogDialog = ({ const [fieldErrors, setFieldErrors] = useState(); const [editItem, setEditItem] = useState(selectedItem); + // Stable handler reference so the memoized ValidatedTextField can skip re-renders const updateFormValue = useMemo( () => updateValue((updater) => @@ -66,71 +67,45 @@ const SensorsAnalogDialog = ({ [setEditItem] ); - // Memoize helper functions to check sensor type conditions - const isCounterOrRate = useMemo( - () => - editItem.t === AnalogType.COUNTER || - editItem.t === AnalogType.RATE || - (editItem.t >= AnalogType.CNT_0 && editItem.t <= AnalogType.CNT_2), - [editItem.t] - ); - const isCounter = useMemo( - () => - editItem.t === AnalogType.COUNTER || - (editItem.t >= AnalogType.CNT_0 && editItem.t <= AnalogType.CNT_2), - [editItem.t] - ); - const isFreqType = useMemo( - () => editItem.t >= AnalogType.FREQ_0 && editItem.t <= AnalogType.FREQ_2, - [editItem.t] - ); - const isPWM = useMemo( - () => - editItem.t === AnalogType.PWM_0 || - editItem.t === AnalogType.PWM_1 || - editItem.t === AnalogType.PWM_2, - [editItem.t] - ); - const isDACOutGPIO = useMemo( - () => - editItem.t === AnalogType.DIGITAL_OUT && - (editItem.g === 25 || editItem.g === 26), - [editItem.t, editItem.g] - ); - const isDigitalOutGPIO = useMemo( - () => - editItem.t === AnalogType.DIGITAL_OUT && - editItem.g !== 25 && - editItem.g !== 26, - [editItem.t, editItem.g] - ); + const isCounterOrRate = + editItem.t === AnalogType.COUNTER || + editItem.t === AnalogType.RATE || + (editItem.t >= AnalogType.CNT_0 && editItem.t <= AnalogType.CNT_2); + const isCounter = + editItem.t === AnalogType.COUNTER || + (editItem.t >= AnalogType.CNT_0 && editItem.t <= AnalogType.CNT_2); + const isFreqType = + editItem.t >= AnalogType.FREQ_0 && editItem.t <= AnalogType.FREQ_2; + const isPWM = + editItem.t === AnalogType.PWM_0 || + editItem.t === AnalogType.PWM_1 || + editItem.t === AnalogType.PWM_2; + const isDACOutGPIO = + editItem.t === AnalogType.DIGITAL_OUT && + (editItem.g === 25 || editItem.g === 26); + const isDigitalOutGPIO = + editItem.t === AnalogType.DIGITAL_OUT && editItem.g !== 25 && editItem.g !== 26; - // Memoize menu items to avoid recreation on each render - const analogTypeMenuItems = useMemo( - () => - AnalogTypeNames.map((val, i) => ({ name: val, value: i + 1 })) - .sort((a, b) => a.name.localeCompare(b.name)) - .map(({ name, value }) => ( - - {name} - - )), - [disabledTypeList] - ); + const analogTypeMenuItems = AnalogTypeNames.map((val, i) => ({ + name: val, + value: i + 1 + })) + .sort((a, b) => a.name.localeCompare(b.name)) + .map(({ name, value }) => ( + + {name} + + )); - const uomMenuItems = useMemo( - () => - DeviceValueUOM_s.map((val, i) => ( - - {val} - - )), - [] - ); + const uomMenuItems = DeviceValueUOM_s.map((val, i) => ( + + {val} + + )); const analogGPIOMenuItems = () => // add selectedItem.g to the list @@ -157,16 +132,16 @@ const SensorsAnalogDialog = ({ } }, [open, selectedItem]); - const handleClose = useCallback( - (_event: React.SyntheticEvent, reason: 'backdropClick' | 'escapeKeyDown') => { - if (reason !== 'backdropClick') { - onClose(); - } - }, - [onClose] - ); + const handleClose = ( + _event: React.SyntheticEvent, + reason: 'backdropClick' | 'escapeKeyDown' + ) => { + if (reason !== 'backdropClick') { + onClose(); + } + }; - const save = useCallback(async () => { + const save = async () => { try { setFieldErrors(undefined); await validate(validator, editItem); @@ -174,17 +149,13 @@ const SensorsAnalogDialog = ({ } catch (error) { setFieldErrors((error as ValidationError).fieldErrors); } - }, [validator, editItem, onSave]); + }; - const remove = useCallback(() => { + const remove = () => { onSave({ ...editItem, d: true }); - }, [editItem, onSave]); + }; - const dialogTitle = useMemo( - () => - `${creating ? LL.ADD(1) + ' ' + LL.NEW(0) : LL.EDIT()} ${LL.ANALOG_SENSOR(0)}`, - [creating, LL] - ); + const dialogTitle = `${creating ? LL.ADD(1) + ' ' + LL.NEW(0) : LL.EDIT()} ${LL.ANALOG_SENSOR(0)}`; return ( diff --git a/interface/src/app/main/SensorsTemperatureDialog.tsx b/interface/src/app/main/SensorsTemperatureDialog.tsx index 670b34244..21a422a77 100644 --- a/interface/src/app/main/SensorsTemperatureDialog.tsx +++ b/interface/src/app/main/SensorsTemperatureDialog.tsx @@ -1,4 +1,4 @@ -import { useCallback, useEffect, useMemo, useState } from 'react'; +import { useEffect, useMemo, useState } from 'react'; import CancelIcon from '@mui/icons-material/Cancel'; import DoneIcon from '@mui/icons-material/Done'; @@ -50,6 +50,7 @@ const SensorsTemperatureDialog = ({ const [fieldErrors, setFieldErrors] = useState(); const [editItem, setEditItem] = useState(selectedItem); + // Stable handler reference so the memoized ValidatedTextField can skip re-renders const updateFormValue = useMemo( () => updateValue( @@ -69,16 +70,13 @@ const SensorsTemperatureDialog = ({ } }, [open, selectedItem]); - const handleClose = useCallback( - (_event: React.SyntheticEvent, reason?: string) => { - if (reason !== 'backdropClick') { - onClose(); - } - }, - [onClose] - ); + const handleClose = (_event: React.SyntheticEvent, reason?: string) => { + if (reason !== 'backdropClick') { + onClose(); + } + }; - const save = useCallback(async () => { + const save = async () => { try { setFieldErrors(undefined); await validate(validator, editItem); @@ -86,29 +84,11 @@ const SensorsTemperatureDialog = ({ } catch (error) { setFieldErrors((error as ValidationError).fieldErrors); } - }, [validator, editItem, onSave]); - - const dialogTitle = useMemo(() => `${LL.EDIT()} ${LL.TEMP_SENSOR()}`, [LL]); - - const offsetValue = useMemo(() => numberValue(editItem.o), [editItem.o]); - - const slotProps = useMemo( - () => ({ - input: { - startAdornment: {TEMP_UNIT} - }, - htmlInput: { - min: OFFSET_MIN, - max: OFFSET_MAX, - step: OFFSET_STEP - } - }), - [] - ); + }; return ( - {dialogTitle} + {`${LL.EDIT()} ${LL.TEMP_SENSOR()}`} {LL.ID_OF(LL.SENSOR(0))}: {editItem.id} @@ -128,12 +108,23 @@ const SensorsTemperatureDialog = ({ {TEMP_UNIT} + ) + }, + htmlInput: { + min: OFFSET_MIN, + max: OFFSET_MAX, + step: OFFSET_STEP + } + }} />
diff --git a/interface/src/app/main/UserProfile.tsx b/interface/src/app/main/UserProfile.tsx index 46928e239..ebcae0be3 100644 --- a/interface/src/app/main/UserProfile.tsx +++ b/interface/src/app/main/UserProfile.tsx @@ -1,4 +1,4 @@ -import { memo, useCallback, useContext } from 'react'; +import { memo, useContext } from 'react'; import PersonIcon from '@mui/icons-material/Person'; import { @@ -23,9 +23,9 @@ const UserProfileComponent = () => { useLayoutTitle(LL.USER_PROFILE()); - const handleSignOut = useCallback(() => { + const handleSignOut = () => { signOut(true); - }, [signOut]); + }; return ( diff --git a/interface/src/app/settings/APSettings.tsx b/interface/src/app/settings/APSettings.tsx index 7d8c8875c..257cc0d81 100644 --- a/interface/src/app/settings/APSettings.tsx +++ b/interface/src/app/settings/APSettings.tsx @@ -1,4 +1,4 @@ -import { useCallback, useMemo, useState } from 'react'; +import { useState } from 'react'; import CancelIcon from '@mui/icons-material/Cancel'; import WarningIcon from '@mui/icons-material/Warning'; @@ -63,22 +63,16 @@ const APSettings = () => { const [fieldErrors, setFieldErrors] = useState(); - const updateFormValue = useMemo( - () => - updateValueDirty( - origData as unknown as Record, - dirtyFlags, - setDirtyFlags, - updateDataValue as (value: unknown) => void - ), - [origData, dirtyFlags, setDirtyFlags, updateDataValue] + const updateFormValue = updateValueDirty( + origData as unknown as Record, + dirtyFlags, + setDirtyFlags, + updateDataValue as (value: unknown) => void ); - // Memoize AP enabled state - const apEnabled = useMemo(() => (data ? isAPEnabled(data) : false), [data]); + const apEnabled = data ? isAPEnabled(data) : false; - // Memoize validation and submit handler - const validateAndSubmit = useCallback(async () => { + const validateAndSubmit = async () => { if (!data) return; try { @@ -88,7 +82,7 @@ const APSettings = () => { } catch (error) { setFieldErrors((error as ValidationError).fieldErrors); } - }, [data, saveData]); + }; const content = () => { if (!data) { diff --git a/interface/src/app/settings/ApplicationSettings.tsx b/interface/src/app/settings/ApplicationSettings.tsx index ce985530b..2135a66f6 100644 --- a/interface/src/app/settings/ApplicationSettings.tsx +++ b/interface/src/app/settings/ApplicationSettings.tsx @@ -1,4 +1,4 @@ -import { useCallback, useMemo, useState } from 'react'; +import { useState } from 'react'; import { toast } from 'react-toastify'; import CancelIcon from '@mui/icons-material/Cancel'; @@ -106,49 +106,36 @@ const ApplicationSettings = () => { }); }); - // Memoized input props to prevent recreation on every render - const SecondsInputProps = useMemo( - () => ({ - endAdornment: {LL.SECONDS()} - }), - [LL] - ); + const SecondsInputProps = { + endAdornment: {LL.SECONDS()} + }; - const MinutesInputProps = useMemo( - () => ({ - endAdornment: {LL.MINUTES()} - }), - [LL] - ); + const MinutesInputProps = { + endAdornment: {LL.MINUTES()} + }; - const HoursInputProps = useMemo( - () => ({ - endAdornment: {LL.HOURS()} - }), - [LL] - ); + const HoursInputProps = { + endAdornment: {LL.HOURS()} + }; - const doRestart = useCallback(async () => { + const doRestart = async () => { setRestarting(true); await sendAPI({ device: 'system', cmd: 'restart', id: 0 }).catch( (error: Error) => { toast.error(error.message); } ); - }, [sendAPI]); + }; - const updateBoardProfile = useCallback( - async (board_profile: string) => { - await readBoardProfile(board_profile).catch((error: Error) => { - toast.error(error.message); - }); - }, - [readBoardProfile] - ); + const updateBoardProfile = async (board_profile: string) => { + await readBoardProfile(board_profile).catch((error: Error) => { + toast.error(error.message); + }); + }; useLayoutTitle(LL.APPLICATION()); - const validateAndSubmit = useCallback(async () => { + const validateAndSubmit = async () => { try { setFieldErrors(undefined); await validate(createSettingsValidator(data), data); @@ -157,31 +144,27 @@ const ApplicationSettings = () => { } finally { await saveData(); } - }, [data, saveData]); + }; - const changeBoardProfile = useCallback( - (event: React.ChangeEvent) => { - const boardProfile = event.target.value; - updateFormValue(event); - if (boardProfile === 'CUSTOM') { - updateDataValue({ - ...data, - board_profile: boardProfile - }); - } else { - void updateBoardProfile(boardProfile); - } - }, - [data, updateBoardProfile, updateFormValue, updateDataValue] - ); + const changeBoardProfile = (event: React.ChangeEvent) => { + const boardProfile = event.target.value; + updateFormValue(event); + if (boardProfile === 'CUSTOM') { + updateDataValue({ + ...data, + board_profile: boardProfile + }); + } else { + void updateBoardProfile(boardProfile); + } + }; - const restart = useCallback(async () => { + const restart = async () => { await validateAndSubmit(); await doRestart(); - }, [validateAndSubmit, doRestart]); + }; - // Memoize board profile select items to prevent recreation - const boardProfileItems = useMemo(() => boardProfileSelectItems(), []); + const boardProfileItems = boardProfileSelectItems(); const content = () => { if (!data || !hardwareData) { diff --git a/interface/src/app/settings/DownloadUpload.tsx b/interface/src/app/settings/DownloadUpload.tsx index 02a373e12..b47b7f242 100644 --- a/interface/src/app/settings/DownloadUpload.tsx +++ b/interface/src/app/settings/DownloadUpload.tsx @@ -1,4 +1,4 @@ -import { useCallback, useMemo, useState } from 'react'; +import { useState } from 'react'; import { toast } from 'react-toastify'; import CancelIcon from '@mui/icons-material/Cancel'; @@ -57,7 +57,7 @@ const DownloadUpload = () => { const { data, send: loadData, error } = useRequest(SystemApi.readSystemStatus); - const doRestart = useCallback(async () => { + const doRestart = async () => { setRestarting(true); try { await sendAPI({ device: 'system', cmd: 'restart', id: 0 }); @@ -65,16 +65,33 @@ const DownloadUpload = () => { toast.error((error as Error).message); setRestarting(false); } - }, [sendAPI]); + }; useLayoutTitle(LL.DOWNLOAD_UPLOAD()); - const handleCloseBackupDialog = useCallback(() => { + const handleCloseBackupDialog = () => { setConfirmBackup(false); - }, []); + }; - const renderBackupDialog = useMemo( - () => ( + const handleDownload = (type: string) => () => { + void sendExportData(type); + setConfirmBackup(false); + }; + + if (restarting) { + return ; + } + + if (!data) { + return ( + + + + ); + } + + return ( + { - ), - [confirmBackup, handleCloseBackupDialog, LL] - ); - - const handleDownload = useCallback( - (type: string) => () => { - void sendExportData(type); - setConfirmBackup(false); - }, - [sendExportData] - ); - - if (restarting) { - return ; - } - - if (!data) { - return ( - - - - ); - } - - return ( - - {renderBackupDialog} {LL.DOWNLOAD(0)} diff --git a/interface/src/app/settings/MqttSettings.tsx b/interface/src/app/settings/MqttSettings.tsx index 417fa8190..a1e8efe18 100644 --- a/interface/src/app/settings/MqttSettings.tsx +++ b/interface/src/app/settings/MqttSettings.tsx @@ -1,4 +1,4 @@ -import { useCallback, useMemo, useState } from 'react'; +import { useState } from 'react'; import { toast } from 'react-toastify'; import CancelIcon from '@mui/icons-material/Cancel'; @@ -57,7 +57,7 @@ const MqttSettings = () => { const [fieldErrors, setFieldErrors] = useState(); - const sendResetMQTT = useCallback(() => { + const sendResetMQTT = () => { void callAction({ action: 'resetMQTT' }) .then(() => { toast.success('MQTT ' + LL.REFRESH() + ' successful'); @@ -65,29 +65,20 @@ const MqttSettings = () => { .catch((error) => { toast.error(String(error.error?.message || 'An error occurred')); }); - }, []); + }; - const updateFormValue = useMemo( - () => - updateValueDirty( - origData as unknown as Record, - dirtyFlags, - setDirtyFlags, - updateDataValue as (value: unknown) => void - ), - [origData, dirtyFlags, setDirtyFlags, updateDataValue] + const updateFormValue = updateValueDirty( + origData as unknown as Record, + dirtyFlags, + setDirtyFlags, + updateDataValue as (value: unknown) => void ); - const SecondsInputProps = useMemo( - () => ({ - endAdornment: {LL.SECONDS()} - }), - [LL] - ); + const SecondsInputProps = { + endAdornment: {LL.SECONDS()} + }; - const emptyFieldErrors = useMemo(() => ({}), []); - - const validateAndSubmit = useCallback(async () => { + const validateAndSubmit = async () => { if (!data) return; try { setFieldErrors(undefined); @@ -96,25 +87,22 @@ const MqttSettings = () => { } catch (error) { setFieldErrors((error as ValidationError).fieldErrors); } - }, [data, saveData]); + }; - const publishIntervalFields = useMemo( - () => [ - { name: 'publish_time_heartbeat', label: 'Heartbeat', validated: true }, - { name: 'publish_time_boiler', label: LL.MQTT_INT_BOILER(), validated: false }, - { - name: 'publish_time_thermostat', - label: LL.MQTT_INT_THERMOSTATS(), - validated: false - }, - { name: 'publish_time_solar', label: LL.MQTT_INT_SOLAR(), validated: false }, - { name: 'publish_time_mixer', label: LL.MQTT_INT_MIXER(), validated: false }, - { name: 'publish_time_water', label: LL.MQTT_INT_WATER(), validated: false }, - { name: 'publish_time_sensor', label: LL.SENSORS(), validated: false }, - { name: 'publish_time_other', label: LL.DEFAULT(0), validated: false } - ], - [LL] - ); + const publishIntervalFields = [ + { name: 'publish_time_heartbeat', label: 'Heartbeat', validated: true }, + { name: 'publish_time_boiler', label: LL.MQTT_INT_BOILER(), validated: false }, + { + name: 'publish_time_thermostat', + label: LL.MQTT_INT_THERMOSTATS(), + validated: false + }, + { name: 'publish_time_solar', label: LL.MQTT_INT_SOLAR(), validated: false }, + { name: 'publish_time_mixer', label: LL.MQTT_INT_MIXER(), validated: false }, + { name: 'publish_time_water', label: LL.MQTT_INT_WATER(), validated: false }, + { name: 'publish_time_sensor', label: LL.SENSORS(), validated: false }, + { name: 'publish_time_other', label: LL.DEFAULT(0), validated: false } + ]; if (!data) { return ( @@ -154,7 +142,7 @@ const MqttSettings = () => { { { { { {field.validated ? ( { const { LL } = useI18nContext(); useLayoutTitle('NTP'); - // Memoized timezone select items for better performance const timeZoneItems = useTimeZoneSelectItems(); - // Memoized selected timezone value - const selectedTzValue = useMemo( - () => (data ? selectedTimeZone(data.tz_label, data.tz_format) : undefined), - [data?.tz_label, data?.tz_format] - ); + const selectedTzValue = data + ? selectedTimeZone(data.tz_label, data.tz_format) + : undefined; const [localTime, setLocalTime] = useState(''); const [settingTime, setSettingTime] = useState(false); @@ -82,32 +79,22 @@ const NTPSettings = () => { } ); - // Memoize updateFormValue to prevent recreation on every render - const updateFormValue = useMemo( - () => - updateValueDirty( - origData as unknown as Record, - dirtyFlags, - setDirtyFlags, - updateDataValue as (value: unknown) => void - ), - [origData, dirtyFlags, setDirtyFlags, updateDataValue] + const updateFormValue = updateValueDirty( + origData as unknown as Record, + dirtyFlags, + setDirtyFlags, + updateDataValue as (value: unknown) => void ); - // Memoize updateLocalTime handler - const updateLocalTime = useCallback( - (event: React.ChangeEvent) => setLocalTime(event.target.value), - [] - ); + const updateLocalTime = (event: React.ChangeEvent) => + setLocalTime(event.target.value); - // Memoize openSetTime handler - const openSetTime = useCallback(() => { + const openSetTime = () => { setLocalTime(formatLocalDateTime(new Date())); setSettingTime(true); - }, []); + }; - // Memoize configureTime handler - const configureTime = useCallback(async () => { + const configureTime = async () => { setProcessing(true); try { @@ -120,13 +107,11 @@ const NTPSettings = () => { } finally { setProcessing(false); } - }, [localTime, updateTime, LL, loadData]); + }; - // Memoize close dialog handler - const handleCloseSetTime = useCallback(() => setSettingTime(false), []); + const handleCloseSetTime = () => setSettingTime(false); - // Memoize validate and submit handler - const validateAndSubmit = useCallback(async () => { + const validateAndSubmit = async () => { if (!data) return; try { setFieldErrors(undefined); @@ -135,23 +120,18 @@ const NTPSettings = () => { } catch (error) { setFieldErrors((error as ValidationError).fieldErrors); } - }, [data, saveData]); + }; - // Memoize timezone change handler - const changeTimeZone = useCallback( - (event: React.ChangeEvent) => { - void updateState(readNTPSettings(), (settings: NTPSettingsType) => ({ - ...settings, - tz_label: event.target.value, - tz_format: TIME_ZONES[event.target.value] - })); - updateFormValue(event); - }, - [updateFormValue] - ); + const changeTimeZone = (event: React.ChangeEvent) => { + void updateState(readNTPSettings(), (settings: NTPSettingsType) => ({ + ...settings, + tz_label: event.target.value, + tz_format: TIME_ZONES[event.target.value] + })); + updateFormValue(event); + }; - // Memoize render content to prevent unnecessary re-renders - const renderContent = useMemo(() => { + const renderContent = () => { if (!data) { return ; } @@ -236,26 +216,12 @@ const NTPSettings = () => { )} ); - }, [ - data, - errorMessage, - loadData, - updateFormValue, - fieldErrors, - selectedTzValue, - changeTimeZone, - timeZoneItems, - dirtyFlags, - openSetTime, - saving, - validateAndSubmit, - LL - ]); + }; return ( {blocker ? : null} - {renderContent} + {renderContent()} {LL.SET_TIME(1)} diff --git a/interface/src/app/settings/Settings.tsx b/interface/src/app/settings/Settings.tsx index a8efc8c4b..7a31f66e7 100644 --- a/interface/src/app/settings/Settings.tsx +++ b/interface/src/app/settings/Settings.tsx @@ -1,4 +1,4 @@ -import { useCallback, useMemo, useState } from 'react'; +import { useState } from 'react'; import AccessTimeIcon from '@mui/icons-material/AccessTime'; import CancelIcon from '@mui/icons-material/Cancel'; @@ -43,148 +43,141 @@ const Settings = () => { immediate: false }); - const doFormat = useCallback(async () => { + const doFormat = async () => { await sendAPI({ device: 'system', cmd: 'format', id: 0 }).then(() => { setRestarting(true); setConfirmFactoryReset(false); }); - }, [sendAPI]); + }; - const handleFactoryResetClose = useCallback(() => { + const handleFactoryResetClose = () => { setConfirmFactoryReset(false); - }, []); + }; - const handleFactoryResetClick = useCallback(() => { + const handleFactoryResetClick = () => { setConfirmFactoryReset(true); - }, []); + }; - const content = useMemo(() => { - return ( - <> - - + if (restarting) { + return ; + } - + return ( + + + - + - + - + - + - + - - + - - {LL.FACTORY_RESET()} - {LL.SYSTEM_FACTORY_TEXT_DIALOG()} - - - - - + + - - - + + {LL.FACTORY_RESET()} + {LL.SYSTEM_FACTORY_TEXT_DIALOG()} + + - - - ); - }, [ - LL, - handleFactoryResetClick, - handleFactoryResetClose, - doFormat, - confirmFactoryReset, - restarting - ]); + + - return restarting ? : {content}; + + + + + + + ); }; export default Settings; diff --git a/interface/src/app/settings/TZ.tsx b/interface/src/app/settings/TZ.tsx index c734f1809..e0ff35294 100644 --- a/interface/src/app/settings/TZ.tsx +++ b/interface/src/app/settings/TZ.tsx @@ -1,5 +1,3 @@ -import { useMemo } from 'react'; - import { MenuItem } from '@mui/material'; export const TIME_ZONES: Record = { @@ -472,26 +470,16 @@ export function selectedTimeZone(label: string, format: string) { return TIME_ZONES[label] === format ? label : undefined; } -// Memoized version for use in components -export function useTimeZoneSelectItems() { - return useMemo( - () => - TIME_ZONE_LABELS.map((label) => ( - - {label} - - )), - [] - ); -} - -// Fallback export for backward compatibility - now memoized const precomputedTimeZoneItems = TIME_ZONE_LABELS.map((label) => ( {label} )); +export function useTimeZoneSelectItems() { + return precomputedTimeZoneItems; +} + export function timeZoneSelectItems() { return precomputedTimeZoneItems; } diff --git a/interface/src/app/settings/network/Network.tsx b/interface/src/app/settings/network/Network.tsx index 300010e3f..e90e5b835 100644 --- a/interface/src/app/settings/network/Network.tsx +++ b/interface/src/app/settings/network/Network.tsx @@ -1,4 +1,4 @@ -import { memo, useCallback, useMemo, useState } from 'react'; +import { memo, useState } from 'react'; import { Navigate, Route, @@ -40,26 +40,20 @@ const Network = () => { const [selectedNetwork, setSelectedNetwork] = useState(); - const selectNetwork = useCallback( - (network: WiFiNetwork) => { - setSelectedNetwork(network); - void navigate('/settings/network/settings'); - }, - [navigate] - ); + const selectNetwork = (network: WiFiNetwork) => { + setSelectedNetwork(network); + void navigate('/settings/network/settings'); + }; - const deselectNetwork = useCallback(() => { + const deselectNetwork = () => { setSelectedNetwork(undefined); - }, []); + }; - const contextValue = useMemo( - () => ({ - ...(selectedNetwork && { selectedNetwork }), - selectNetwork, - deselectNetwork - }), - [selectedNetwork, selectNetwork, deselectNetwork] - ); + const contextValue = { + ...(selectedNetwork && { selectedNetwork }), + selectNetwork, + deselectNetwork + }; return ( diff --git a/interface/src/app/settings/network/NetworkSettings.tsx b/interface/src/app/settings/network/NetworkSettings.tsx index 5f96aa594..89789ccc2 100644 --- a/interface/src/app/settings/network/NetworkSettings.tsx +++ b/interface/src/app/settings/network/NetworkSettings.tsx @@ -121,19 +121,19 @@ const NetworkSettings = () => { deselectNetwork(); }, [data, saveData, deselectNetwork]); - const setCancel = useCallback(async () => { + const setCancel = async () => { deselectNetwork(); await loadData(); - }, [deselectNetwork, loadData]); + }; - const doRestart = useCallback(async () => { + const doRestart = async () => { setRestarting(true); await sendAPI({ device: 'system', cmd: 'restart', id: 0 }).catch( (error: Error) => { toast.error(error.message); } ); - }, [sendAPI]); + }; const content = () => { if (!data) { diff --git a/interface/src/app/settings/network/WiFiNetworkScanner.tsx b/interface/src/app/settings/network/WiFiNetworkScanner.tsx index b4517b0f1..b7d58206c 100644 --- a/interface/src/app/settings/network/WiFiNetworkScanner.tsx +++ b/interface/src/app/settings/network/WiFiNetworkScanner.tsx @@ -1,4 +1,4 @@ -import { memo, useCallback, useRef, useState } from 'react'; +import { memo, useRef, useState } from 'react'; import PermScanWifiIcon from '@mui/icons-material/PermScanWifi'; import { Button } from '@mui/material'; @@ -48,12 +48,12 @@ const WiFiNetworkScanner = () => { } }); - const renderNetworkScanner = useCallback(() => { + const renderNetworkScanner = () => { if (!networkList) { return ; } return ; - }, [networkList, errorMessage]); + }; return ( diff --git a/interface/src/app/settings/network/WiFiNetworkSelector.tsx b/interface/src/app/settings/network/WiFiNetworkSelector.tsx index bfc0f9949..e7b7d327b 100644 --- a/interface/src/app/settings/network/WiFiNetworkSelector.tsx +++ b/interface/src/app/settings/network/WiFiNetworkSelector.tsx @@ -1,4 +1,4 @@ -import { memo, useCallback, useContext } from 'react'; +import { memo, useContext } from 'react'; import LockIcon from '@mui/icons-material/Lock'; import LockOpenIcon from '@mui/icons-material/LockOpen'; @@ -63,34 +63,31 @@ const WiFiNetworkSelector = ({ networkList }: { networkList: WiFiNetworkList }) const wifiConnectionContext = useContext(WiFiConnectionContext); - const renderNetwork = useCallback( - (network: WiFiNetwork) => ( - wifiConnectionContext.selectNetwork(network)} - > - - {isNetworkOpen(network) ? : } - - - - - - - - - ), - [wifiConnectionContext, theme] + const renderNetwork = (network: WiFiNetwork) => ( + wifiConnectionContext.selectNetwork(network)} + > + + {isNetworkOpen(network) ? : } + + + + + + + + ); if (networkList.networks.length === 0) { diff --git a/interface/src/app/settings/security/ManageUsers.tsx b/interface/src/app/settings/security/ManageUsers.tsx index bc17261d8..9dec88ee9 100644 --- a/interface/src/app/settings/security/ManageUsers.tsx +++ b/interface/src/app/settings/security/ManageUsers.tsx @@ -99,34 +99,28 @@ const ManageUsers = () => { [] ); - const noAdminConfigured = useCallback( - () => !data?.users.find((u) => u.admin), - [data] - ); + const noAdminConfigured = () => !data?.users.find((u) => u.admin); - const removeUser = useCallback( - (toRemove: UserType) => { - if (!data) return; - const users = data.users.filter((u) => u.username !== toRemove.username); - updateDataValue({ ...data, users }); - setChanged(changed + 1); - }, - [data, updateDataValue, changed] - ); + const removeUser = (toRemove: UserType) => { + if (!data) return; + const users = data.users.filter((u) => u.username !== toRemove.username); + updateDataValue({ ...data, users }); + setChanged(changed + 1); + }; - const createUser = useCallback(() => { + const createUser = () => { setCreating(true); setUser({ username: '', password: '', admin: true }); - }, []); + }; - const editUser = useCallback((toEdit: UserType) => { + const editUser = (toEdit: UserType) => { setCreating(false); setUser({ ...toEdit }); - }, []); + }; const cancelEditingUser = useCallback(() => { setUser(undefined); @@ -150,20 +144,20 @@ const ManageUsers = () => { setGeneratingToken(undefined); }, []); - const generateTokenForUser = useCallback((username: string) => { + const generateTokenForUser = (username: string) => { setGeneratingToken(username); - }, []); + }; - const onSubmit = useCallback(async () => { + const onSubmit = async () => { await saveData(); await authenticatedContext.refresh(); setChanged(0); - }, [saveData, authenticatedContext]); + }; - const onCancelSubmit = useCallback(async () => { + const onCancelSubmit = async () => { await loadData(); setChanged(0); - }, [loadData]); + }; const content = () => { if (!data) { @@ -177,15 +171,10 @@ const ManageUsers = () => { admin: boolean; } - // add id to the type, needed for the table - const user_table = useMemo( - () => - data.users.map((u) => ({ - ...u, - id: u.username - })) as UserType2[], - [data.users] - ); + const user_table = data.users.map((u) => ({ + ...u, + id: u.username + })) as UserType2[]; return ( <> diff --git a/interface/src/app/settings/security/Security.tsx b/interface/src/app/settings/security/Security.tsx index 012aac55a..5701a907c 100644 --- a/interface/src/app/settings/security/Security.tsx +++ b/interface/src/app/settings/security/Security.tsx @@ -1,4 +1,4 @@ -import { memo, useMemo } from 'react'; +import { memo } from 'react'; import { Navigate, Route, Routes, matchRoutes, useLocation } from 'react-router'; import { Tab } from '@mui/material'; @@ -15,19 +15,15 @@ const Security = () => { const location = useLocation(); - const matchedRoutes = useMemo( - () => - matchRoutes( - [ - { - path: '/settings/security/settings', - element: - }, - { path: '/settings/security/users', element: } - ], - location - ), - [location] + const matchedRoutes = matchRoutes( + [ + { + path: '/settings/security/settings', + element: + }, + { path: '/settings/security/users', element: } + ], + location ); const routerTab = matchedRoutes?.[0]?.route.path || false; diff --git a/interface/src/app/settings/security/User.tsx b/interface/src/app/settings/security/User.tsx index bf1ce8862..093ad5d33 100644 --- a/interface/src/app/settings/security/User.tsx +++ b/interface/src/app/settings/security/User.tsx @@ -1,4 +1,4 @@ -import { memo, useCallback, useEffect, useState } from 'react'; +import { memo, useEffect, useState } from 'react'; import type { FC } from 'react'; import CancelIcon from '@mui/icons-material/Cancel'; @@ -62,7 +62,7 @@ const User: FC = ({ } }, [open]); - const validateAndDone = useCallback(async () => { + const validateAndDone = async () => { if (user) { try { setFieldErrors(undefined); @@ -72,7 +72,7 @@ const User: FC = ({ setFieldErrors((error as ValidationError).fieldErrors); } } - }, [user, validator, onDoneEditing]); + }; return ( { useLayoutTitle(LL.DATA_TRAFFIC()); - const stats_theme = tableTheme( - useMemo( - () => ({ - Table: ` + const stats_theme = tableTheme({ + Table: ` --data-table-library_grid-template-columns: repeat(1, minmax(0, 1fr)) 90px 90px 80px; `, - BaseRow: ` + BaseRow: ` font-size: 14px; `, - HeaderRow: ` + HeaderRow: ` text-transform: uppercase; background-color: black; color: #90CAF9; @@ -55,7 +51,7 @@ const SystemActivity = () => { border-bottom: 1px solid #565656; } `, - Row: ` + Row: ` .td { padding: 8px; border-top: 1px solid #565656; @@ -69,26 +65,20 @@ const SystemActivity = () => { background-color: #1e1e1e; } `, - BaseCell: ` + BaseCell: ` &:not(:first-of-type) { text-align: center; } ` - }), - [] - ) - ); + }); - const showName = useCallback( - (id: number) => { - const name: keyof Translation['STATUS_NAMES'] = - id.toString() as keyof Translation['STATUS_NAMES']; - return LL.STATUS_NAMES[name](); - }, - [LL] - ); + const showName = (id: number) => { + const name: keyof Translation['STATUS_NAMES'] = + id.toString() as keyof Translation['STATUS_NAMES']; + return LL.STATUS_NAMES[name](); + }; - const showQuality = useCallback((stat: Stat) => { + const showQuality = (stat: Stat) => { if (stat.q === 0 || stat.s + stat.f === 0) { return; } @@ -100,14 +90,18 @@ const SystemActivity = () => { } else { return
{stat.q}%
; } - }, []); - - const content = useMemo(() => { - if (!data) { - return ; - } + }; + if (!data) { return ( + + + + ); + } + + return ( + { )}
- ); - }, [data, loadData, error?.message, stats_theme, LL, showName, showQuality]); - - return {content}; +
+ ); }; export default SystemActivity; diff --git a/interface/src/app/status/MqttStatus.tsx b/interface/src/app/status/MqttStatus.tsx index 7e38b633b..37b2507e4 100644 --- a/interface/src/app/status/MqttStatus.tsx +++ b/interface/src/app/status/MqttStatus.tsx @@ -1,4 +1,4 @@ -import { type FC, memo, useMemo } from 'react'; +import { type FC, memo } from 'react'; import AutoAwesomeMotionIcon from '@mui/icons-material/AutoAwesomeMotion'; import DeviceHubIcon from '@mui/icons-material/DeviceHub'; @@ -127,16 +127,15 @@ const MqttStatus = () => { void loadData(); }); - // Memoize error message separately to avoid re-renders on error object changes const errorMessage = error?.message || ''; - const mqttStatusText = useMemo(() => { - if (!data) return ''; - if (!data.enabled) return LL.NOT_ENABLED(); - return data.connected - ? `${LL.CONNECTED(0)} (${data.connect_count})` - : `${LL.DISCONNECTED()} (${data.connect_count})`; - }, [data, LL]); + const mqttStatusText = !data + ? '' + : !data.enabled + ? LL.NOT_ENABLED() + : data.connected + ? `${LL.CONNECTED(0)} (${data.connect_count})` + : `${LL.DISCONNECTED()} (${data.connect_count})`; if (!data) { return ( diff --git a/interface/src/app/status/NTPStatus.tsx b/interface/src/app/status/NTPStatus.tsx index 8dc019d67..46c234b7d 100644 --- a/interface/src/app/status/NTPStatus.tsx +++ b/interface/src/app/status/NTPStatus.tsx @@ -1,5 +1,3 @@ -import { useMemo } from 'react'; - import AccessTimeIcon from '@mui/icons-material/AccessTime'; import DnsIcon from '@mui/icons-material/Dns'; import SwapVerticalCircleIcon from '@mui/icons-material/SwapVerticalCircle'; @@ -67,12 +65,16 @@ const NTPStatus = () => { } }; - const content = useMemo(() => { - if (!data) { - return ; - } - + if (!data) { return ( + + + + ); + } + + return ( + @@ -121,10 +123,8 @@ const NTPStatus = () => { - ); - }, [data, error, loadData, LL, theme]); - - return {content}; + + ); }; export default NTPStatus; diff --git a/interface/src/app/status/NetworkStatus.tsx b/interface/src/app/status/NetworkStatus.tsx index 6172c8186..e72dcac95 100644 --- a/interface/src/app/status/NetworkStatus.tsx +++ b/interface/src/app/status/NetworkStatus.tsx @@ -1,5 +1,3 @@ -import { useMemo } from 'react'; - import DeviceHubIcon from '@mui/icons-material/DeviceHub'; import DnsIcon from '@mui/icons-material/Dns'; import GiteIcon from '@mui/icons-material/Gite'; @@ -124,16 +122,20 @@ const NetworkStatus = () => { const theme = useTheme(); - const content = useMemo(() => { - if (!data) { - return ; - } - - const statusText = getNetworkStatusText(data.status, data.reconnect_count, LL); - const statusColor = networkStatusHighlight(data, theme); - const qualityColor = networkQualityHighlight(data, theme); - + if (!data) { return ( + + + + ); + } + + const statusText = getNetworkStatusText(data.status, data.reconnect_count, LL); + const statusColor = networkStatusHighlight(data, theme); + const qualityColor = networkQualityHighlight(data, theme); + + return ( + @@ -227,10 +229,8 @@ const NetworkStatus = () => { )} - ); - }, [data, error, loadData, LL, theme]); - - return {content}; + + ); }; export default NetworkStatus; diff --git a/interface/src/app/status/Status.tsx b/interface/src/app/status/Status.tsx index 1d87a5d4d..e5a08e1f5 100644 --- a/interface/src/app/status/Status.tsx +++ b/interface/src/app/status/Status.tsx @@ -1,4 +1,4 @@ -import { useCallback, useContext, useMemo, useState } from 'react'; +import { useContext, useState } from 'react'; import { toast } from 'react-toastify'; import AccessTimeIcon from '@mui/icons-material/AccessTime'; @@ -43,7 +43,6 @@ import { formatDateTime } from 'utils/time'; import SystemMonitor from './SystemMonitor'; -// Pure functions moved outside component to avoid recreation on each render const formatNumber = (num: number) => new Intl.NumberFormat().format(num); const formatDurationSec = ( @@ -97,10 +96,8 @@ const SystemStatus = () => { const theme = useTheme(); - // Memoize derived status values to avoid recalculation on every render - const busStatus = useMemo(() => { + const busStatus = (() => { if (!data) return 'EMS state unknown'; - switch (data.bus_status) { case busConnectionStatus.BUS_STATUS_CONNECTED: return `EMS ${LL.CONNECTED(0)} (${formatDurationSec(data.bus_uptime, LL)})`; @@ -111,12 +108,10 @@ const SystemStatus = () => { default: return 'EMS state unknown'; } - }, [data?.bus_status, data?.bus_uptime, LL]); + })(); - // Memoize derived status values to avoid recalculation on every render - const systemStatus = useMemo(() => { + const systemStatus = (() => { if (!data) return '??'; - switch (data.status) { case SystemStatusCodes.SYSTEM_STATUS_PENDING_UPLOAD: case SystemStatusCodes.SYSTEM_STATUS_UPLOADING: @@ -129,14 +124,12 @@ const SystemStatus = () => { case SystemStatusCodes.SYSTEM_STATUS_INVALID_GPIO: return LL.GPIO_OF(LL.FAILED(0)); default: - // SystemStatusCodes.SYSTEM_STATUS_NORMAL return 'OK'; } - }, [data?.status, LL]); + })(); - const busStatusHighlight = useMemo(() => { + const busStatusHighlight = (() => { if (!data) return theme.palette.warning.main; - switch (data.bus_status) { case busConnectionStatus.BUS_STATUS_TX_ERRORS: return theme.palette.warning.main; @@ -147,11 +140,10 @@ const SystemStatus = () => { default: return theme.palette.warning.main; } - }, [data?.bus_status, theme.palette]); + })(); - const ntpStatus = useMemo(() => { + const ntpStatus = (() => { if (!data) return LL.UNKNOWN(); - switch (data.ntp_status) { case NTPSyncStatus.NTP_DISABLED: return LL.NOT_ENABLED(); @@ -164,11 +156,10 @@ const SystemStatus = () => { default: return LL.UNKNOWN(); } - }, [data?.ntp_status, data?.ntp_time, LL]); + })(); - const ntpStatusHighlight = useMemo(() => { + const ntpStatusHighlight = (() => { if (!data) return theme.palette.error.main; - switch (data.ntp_status) { case NTPSyncStatus.NTP_DISABLED: return theme.palette.info.main; @@ -179,11 +170,10 @@ const SystemStatus = () => { default: return theme.palette.error.main; } - }, [data?.ntp_status, theme.palette]); + })(); - const networkStatusHighlight = useMemo(() => { + const networkStatusHighlight = (() => { if (!data) return theme.palette.warning.main; - switch (data.network_status) { case NetworkConnectionStatus.WIFI_STATUS_IDLE: case NetworkConnectionStatus.WIFI_STATUS_DISCONNECTED: @@ -198,11 +188,10 @@ const SystemStatus = () => { default: return theme.palette.warning.main; } - }, [data?.network_status, theme.palette]); + })(); - const networkStatus = useMemo(() => { + const networkStatus = (() => { if (!data) return LL.UNKNOWN(); - switch (data.network_status) { case NetworkConnectionStatus.WIFI_STATUS_NO_SHIELD: return LL.INACTIVE(1); @@ -223,15 +212,12 @@ const SystemStatus = () => { default: return LL.UNKNOWN(); } - }, [data?.network_status, data?.wifi_rssi, LL]); + })(); - const activeHighlight = useCallback( - (value: boolean) => - value ? theme.palette.success.main : theme.palette.info.main, - [theme.palette] - ); + const activeHighlight = (value: boolean) => + value ? theme.palette.success.main : theme.palette.info.main; - const doRestart = useCallback(async () => { + const doRestart = async () => { setConfirmRestart(false); setRestarting(true); await sendAPI({ device: 'system', cmd: 'restart', id: 0 }).catch( @@ -239,14 +225,123 @@ const SystemStatus = () => { toast.error(error.message); } ); - }, [sendAPI]); + }; - const handleCloseRestartDialog = useCallback(() => { - setConfirmRestart(false); - }, []); + const handleCloseRestartDialog = () => setConfirmRestart(false); + + if (restarting) { + return ; + } + + if (!data || !LL) { + return ( + + + + ); + } + + return ( + + + + + + + + + + + + {me.admin && ( + + )} + + + + + + + + + + + + + + + + - const renderRestartDialog = useMemo( - () => ( { - ), - [confirmRestart, handleCloseRestartDialog, doRestart, LL] + ); - - // Memoize formatted values - const firmwareVersion = useMemo( - () => `v${data?.emsesp_version || ''}`, - [data?.emsesp_version] - ); - - const uptimeText = useMemo( - () => (data ? formatDurationSec(data.uptime, LL) : ''), - [data?.uptime, LL] - ); - - const freeMemoryText = useMemo( - () => (data ? `${formatNumber(data.free_heap)} KB ${LL.FREE_MEMORY()}` : ''), - [data?.free_heap, LL] - ); - - const networkIcon = useMemo( - () => - data?.network_status === NetworkConnectionStatus.WIFI_STATUS_CONNECTED - ? WifiIcon - : RouterIcon, - [data?.network_status] - ); - - const mqttStatusText = useMemo( - () => (data?.mqtt_status ? LL.CONNECTED(0) : LL.INACTIVE(0)), - [data?.mqtt_status, LL] - ); - - const apStatusText = useMemo( - () => (data?.ap_status ? LL.ACTIVE() : LL.INACTIVE(0)), - [data?.ap_status, LL] - ); - - const handleRestartClick = useCallback(() => { - setConfirmRestart(true); - }, []); - - const content = useMemo(() => { - if (!data || !LL) { - return ; - } - - return ( - <> - - - - - - - - - - - {me.admin && ( - - )} - - - - - - - - - - - - - - - - - - {renderRestartDialog} - - ); - }, [ - data, - LL, - firmwareVersion, - uptimeText, - freeMemoryText, - networkIcon, - mqttStatusText, - apStatusText, - busStatus, - busStatusHighlight, - networkStatusHighlight, - networkStatus, - ntpStatusHighlight, - ntpStatus, - activeHighlight, - me.admin, - handleRestartClick, - error, - loadData, - renderRestartDialog - ]); - - return restarting ? : {content}; }; export default SystemStatus; diff --git a/interface/src/app/status/SystemLog.tsx b/interface/src/app/status/SystemLog.tsx index 628e7a2a1..76d384135 100644 --- a/interface/src/app/status/SystemLog.tsx +++ b/interface/src/app/status/SystemLog.tsx @@ -1,11 +1,4 @@ -import { - memo, - useCallback, - useEffect, - useLayoutEffect, - useRef, - useState -} from 'react'; +import { memo, useEffect, useLayoutEffect, useRef, useState } from 'react'; import { toast } from 'react-toastify'; import DownloadIcon from '@mui/icons-material/GetApp'; @@ -185,8 +178,7 @@ const SystemLog = () => { }; }, [data]); // Recalculate when data changes (in case layout shifts) - // Memoize message handler to avoid recreating on every render - const handleLogMessage = useCallback((message: { data: string }) => { + const handleLogMessage = (message: { data: string }) => { const rawData = message.data; const logentry = JSON.parse(rawData) as LogEntry; setLogEntries((log) => { @@ -200,7 +192,7 @@ const SystemLog = () => { const newLog = [...log, logentry]; return newLog; }); - }, []); + }; useSSE(fetchLogES, { immediate: true, @@ -211,7 +203,7 @@ const SystemLog = () => { toast.error('No connection to Log service'); }); - const onDownload = useCallback(() => { + const onDownload = () => { const result = logEntries .map((i) => `${i.t} ${levelLabel(i.l)} ${i.i}: [${i.n}] ${i.m}`) .join('\n'); @@ -225,11 +217,11 @@ const SystemLog = () => { document.body.appendChild(a); a.click(); document.body.removeChild(a); - }, [logEntries]); + }; - const saveSettings = useCallback(async () => { + const saveSettings = async () => { await saveData(); - }, [saveData]); + }; // handle scrolling - optimized to only scroll when needed const ref = useRef(null); @@ -246,7 +238,7 @@ const SystemLog = () => { } }, [logEntries.length, autoscroll]); - const sendReadCommand = useCallback(() => { + const sendReadCommand = () => { if (readValue === '') { setReadOpen(!readOpen); return; @@ -257,7 +249,7 @@ const SystemLog = () => { setReadOpen(false); setReadValue(''); } - }, [readValue, readOpen, send]); + }; const content = () => { if (!data) { diff --git a/interface/src/app/status/SystemMonitor.tsx b/interface/src/app/status/SystemMonitor.tsx index 4f4423be7..b6fa12d1f 100644 --- a/interface/src/app/status/SystemMonitor.tsx +++ b/interface/src/app/status/SystemMonitor.tsx @@ -1,4 +1,4 @@ -import { useCallback, useMemo, useRef, useState } from 'react'; +import { useRef, useState } from 'react'; import CancelIcon from '@mui/icons-material/Cancel'; import { Box, Button, Typography } from '@mui/material'; @@ -57,39 +57,31 @@ const SystemMonitor = () => { void send(); }, 1000); // check every 1 second - const { statusMessage, isUploading, progressValue } = useMemo(() => { - const status = data?.status; + const status = data?.status; - const message = - status && status >= SystemStatusCodes.SYSTEM_STATUS_UPLOADING - ? LL.WAIT_FIRMWARE() - : status === SystemStatusCodes.SYSTEM_STATUS_PENDING_RESTART - ? LL.APPLICATION_RESTARTING() - : status === SystemStatusCodes.SYSTEM_STATUS_NORMAL - ? LL.RESTARTING_PRE() - : status === SystemStatusCodes.SYSTEM_STATUS_ERROR_UPLOAD - ? 'Upload Failed' - : LL.RESTARTING_POST(); + const statusMessage = + status && status >= SystemStatusCodes.SYSTEM_STATUS_UPLOADING + ? LL.WAIT_FIRMWARE() + : status === SystemStatusCodes.SYSTEM_STATUS_PENDING_RESTART + ? LL.APPLICATION_RESTARTING() + : status === SystemStatusCodes.SYSTEM_STATUS_NORMAL + ? LL.RESTARTING_PRE() + : status === SystemStatusCodes.SYSTEM_STATUS_ERROR_UPLOAD + ? 'Upload Failed' + : LL.RESTARTING_POST(); - const uploading = - status !== undefined && status >= SystemStatusCodes.SYSTEM_STATUS_UPLOADING; - const progress = - uploading && status - ? Math.round(status - SystemStatusCodes.SYSTEM_STATUS_UPLOADING) - : 0; + const isUploading = + status !== undefined && status >= SystemStatusCodes.SYSTEM_STATUS_UPLOADING; + const progressValue = + isUploading && status + ? Math.round(status - SystemStatusCodes.SYSTEM_STATUS_UPLOADING) + : 0; - return { - statusMessage: message, - isUploading: uploading, - progressValue: progress - }; - }, [data?.status, LL]); - - const onCancel = useCallback(async () => { + const onCancel = async () => { setErrorMessage(undefined); await setSystemStatus(String(SystemStatusCodes.SYSTEM_STATUS_NORMAL)); document.location.href = '/'; - }, [setSystemStatus]); + }; return ( )} - {version.date && ( + {version && version.date && ( void; onInstall: (url: string) => void; }) => { - const binURL = useMemo(() => { + const binURL = (() => { if (!latestVersion || !latestDevVersion) return ''; - const version = fetchDevVersion ? latestDevVersion : latestVersion; const filename = `EMS-ESP-${version.version.replaceAll('.', '_')}-${platform}.bin`; - return fetchDevVersion ? `${DEV_URL}${filename}` : `${STABLE_URL}v${version.version}/${filename}`; - }, [fetchDevVersion, latestVersion, latestDevVersion, platform]); + })(); return ( @@ -532,396 +530,340 @@ const Version = () => { toast.error(String(error.error?.message || 'An error occurred')); }); - const platform = useMemo(() => (data ? getPlatform(data) : ''), [data]); + const platform = data ? getPlatform(data) : ''; - const otherPartitions = useMemo( - () => data?.partitions.filter((p) => p.partition !== data.partition) ?? [], - [data] - ); + const otherPartitions = + data?.partitions.filter((p) => p.partition !== data.partition) ?? []; - const setPartitionVersionInfo = useCallback( - (partition: string) => { - setShowVersionInfo(3); + const setPartitionVersionInfo = (partition: string) => { + setShowVersionInfo(3); + const partitionData = data?.partitions.find((p) => p.partition === partition); + if (partitionData) { + setPartitionVersion({ + version: partitionData.version, + date: partitionData.install_date ?? '' + }); + setPartition(partitionData.partition); + setFirmwareSize(partitionData.size); + } + }; - // search for the partition in the data.partitions array - const partitionData = data?.partitions.find((p) => p.partition === partition); - if (partitionData) { - setPartitionVersion({ - version: partitionData.version, - date: partitionData.install_date ?? '' - }); - setPartition(partitionData.partition); - setFirmwareSize(partitionData.size); - } - }, - [data] - ); - - const doRestart = useCallback(async () => { + const doRestart = async () => { await sendAPI({ device: 'system', cmd: 'restart', id: 0 }).catch( (error: Error) => { toast.error(error.message); } ); setRestarting(true); - }, [sendAPI]); + }; - const installFirmwareURL = useCallback( - async (url: string) => { - await sendUploadURL(url).catch((error: Error) => { - toast.error(error.message); - }); - await doRestart(); - }, - [sendUploadURL, doRestart] - ); + const installFirmwareURL = async (url: string) => { + await sendUploadURL(url).catch((error: Error) => { + toast.error(error.message); + }); + await doRestart(); + }; - const installPartitionFirmware = useCallback( - async (partition: string) => { - await sendSetPartition(partition).catch((error: Error) => { - toast.error(error.message); - }); - setRestarting(true); - }, - [sendSetPartition] - ); + const installPartitionFirmware = async (partition: string) => { + await sendSetPartition(partition).catch((error: Error) => { + toast.error(error.message); + }); + setRestarting(true); + }; - const showPartitionDialog = useCallback( - (version: string, partition: string, install_date: string) => { - setOpenInstallPartitionDialog(true); - setPartitionVersion({ version: version, date: install_date }); - setPartition(partition); - }, - [] - ); + const showPartitionDialog = ( + version: string, + partition: string, + install_date: string + ) => { + setOpenInstallPartitionDialog(true); + setPartitionVersion({ version: version, date: install_date }); + setPartition(partition); + }; - const showFirmwareDialog = useCallback( - (useDevVersion: boolean) => { - setFetchDevVersion(useDevVersion); - void checkUpgradeImportantMessages( - useDevVersion ? latestDevVersion?.version : latestVersion?.version - ); - setOpenInstallDialog(true); - }, - [latestDevVersion, latestVersion, fetchDevVersion] - ); + const showFirmwareDialog = (useDevVersion: boolean) => { + setFetchDevVersion(useDevVersion); + const targetVersion = useDevVersion + ? latestDevVersion?.version + : latestVersion?.version; + if (targetVersion) { + void checkUpgradeImportantMessages(targetVersion); + } + setOpenInstallDialog(true); + }; - const closeInstallDialog = useCallback(() => { - setOpenInstallDialog(false); - }, []); + const closeInstallDialog = () => setOpenInstallDialog(false); + const closeInstallPartitionDialog = () => setOpenInstallPartitionDialog(false); - const closeInstallPartitionDialog = useCallback(() => { - setOpenInstallPartitionDialog(false); - }, []); - - const handleVersionInfoClose = useCallback(() => { + const handleVersionInfoClose = () => { setShowVersionInfo(0); setPartitionVersion(undefined); setPartition(''); - }, []); + }; useLayoutTitle('EMS-ESP Firmware'); - const showButtons = useCallback( - (showingDev: boolean) => { - const choice = showingDev - ? !usingDevVersion - ? LL.SWITCH_RELEASE_TYPE(LL.DEVELOPMENT()) - : devUpgradeAvailable - ? LL.UPDATE_AVAILABLE() - : undefined - : usingDevVersion - ? LL.SWITCH_RELEASE_TYPE(LL.STABLE()) - : stableUpgradeAvailable - ? LL.UPDATE_AVAILABLE() - : undefined; - - if (!choice) { - return ( - <> - - - {LL.LATEST_VERSION(usingDevVersion ? LL.DEVELOPMENT() : LL.STABLE())} - - - - ); - } - - if (!me.admin) return null; + const showButtons = (showingDev: boolean) => { + const choice = showingDev + ? !usingDevVersion + ? LL.SWITCH_RELEASE_TYPE(LL.DEVELOPMENT()) + : devUpgradeAvailable + ? LL.UPDATE_AVAILABLE() + : undefined + : usingDevVersion + ? LL.SWITCH_RELEASE_TYPE(LL.STABLE()) + : stableUpgradeAvailable + ? LL.UPDATE_AVAILABLE() + : undefined; + if (!choice) { return ( - + <> + + + {LL.LATEST_VERSION(usingDevVersion ? LL.DEVELOPMENT() : LL.STABLE())} + + + ); - }, - [ - usingDevVersion, - devUpgradeAvailable, - stableUpgradeAvailable, - me.admin, - LL, - showFirmwareDialog - ] - ); - - const content = useMemo(() => { - if (!data) { - return ; } + if (!me.admin) return null; + return ( - <> - - - {LL.THIS_VERSION()} - + + ); + }; - - - {LL.VERSION()} - - - - {data.emsesp_version} - {data.build_flags && ( - -   ({data.build_flags}) - - )} - setPartitionVersionInfo(data.partition)} - aria-label={LL.FIRMWARE_VERSION_INFO()} - > - - - - + if (restarting) { + return ; + } - - {LL.PLATFORM()} - - - - {platform} + if (!data) { + return ( + + + + ); + } + + return ( + + + + {LL.THIS_VERSION()} + + + + + {LL.VERSION()} + + + + {data.emsesp_version} + {data.build_flags && ( -   ( - {data.psram ? ( - - ) : ( - - )} - PSRAM) +   ({data.build_flags}) - - + )} + setPartitionVersionInfo(data.partition)} + aria-label={LL.FIRMWARE_VERSION_INFO()} + > + + + - {internetLive ? ( - <> - - {LL.AVAILABLE_VERSION()} - - - - {otherPartitions.length > 0 && data.developer_mode && ( - <> - - - {LL.STORED_VERSIONS()} - - - - {otherPartitions.map((partition) => ( - - {partition.version} - - setPartitionVersionInfo(partition.partition) - } - aria-label={LL.FIRMWARE_VERSION_INFO()} - > - - - - - ))} - - + + {LL.PLATFORM()} + + + + {platform} + +   ( + {data.psram ? ( + + ) : ( + )} - - {LL.STABLE()} - - - - {latestVersion?.version} - setShowVersionInfo(1)} - aria-label={LL.FIRMWARE_VERSION_INFO()} - > - - - {showButtons(false)} - - - - - {LL.DEVELOPMENT()} - - - - {latestDevVersion?.version} - setShowVersionInfo(2)} - aria-label={LL.FIRMWARE_VERSION_INFO()} - > - - - {showButtons(true)} - - - - - ) : ( - - - {LL.INTERNET_CONNECTION_REQUIRED()} - - )} - {me.admin && ( - <> - - - - - {LL.UPLOAD()} + PSRAM) - - - )} - - - ); - }, [ - data, - error, - loadData, - LL, - platform, - internetLive, - latestVersion, - latestDevVersion, - showVersionInfo, - locale, - openInstallDialog, - fetchDevVersion, - downloadOnly, - me.admin, - showButtons, - handleVersionInfoClose, - closeInstallDialog, - installFirmwareURL, - doRestart, - otherPartitions, - setPartitionVersionInfo, - showPartitionDialog, - partitionVersion, - partition, - firmwareSize, - closeInstallPartitionDialog, - installPartitionFirmware - ]); +
+ + - return restarting ? : {content}; + {internetLive ? ( + <> + + {LL.AVAILABLE_VERSION()} + + + + {otherPartitions.length > 0 && data.developer_mode && ( + <> + + {LL.STORED_VERSIONS()} + + + {otherPartitions.map((partition) => ( + + {partition.version} + + setPartitionVersionInfo(partition.partition) + } + aria-label={LL.FIRMWARE_VERSION_INFO()} + > + + + + + ))} + + + )} + + {LL.STABLE()} + + + + {latestVersion?.version} + setShowVersionInfo(1)} + aria-label={LL.FIRMWARE_VERSION_INFO()} + > + + + {showButtons(false)} + + + + + {LL.DEVELOPMENT()} + + + + {latestDevVersion?.version} + setShowVersionInfo(2)} + aria-label={LL.FIRMWARE_VERSION_INFO()} + > + + + {showButtons(true)} + + + + + ) : ( + + + {LL.INTERNET_CONNECTION_REQUIRED()} + + )} + {me.admin && ( + <> + + + + + {LL.UPLOAD()} + + + + )} + +
+ ); }; export default memo(Version); diff --git a/interface/src/components/MessageBox.tsx b/interface/src/components/MessageBox.tsx index aa17e6215..ae7d8e177 100644 --- a/interface/src/components/MessageBox.tsx +++ b/interface/src/components/MessageBox.tsx @@ -1,4 +1,4 @@ -import { type FC, type PropsWithChildren, memo, useMemo } from 'react'; +import { type FC, type PropsWithChildren, memo } from 'react'; import CheckCircleOutlineOutlinedIcon from '@mui/icons-material/CheckCircleOutlineOutlined'; import ErrorIcon from '@mui/icons-material/Error'; @@ -38,18 +38,17 @@ const MessageBox: FC> = ({ }) => { const theme = useTheme(); - const { Icon, backgroundColor } = useMemo(() => { - const Icon = LEVEL_ICONS[level]; - const palettePath = LEVEL_PALETTE_PATHS[level]; - const [key, shade] = palettePath.split('.') as [ - keyof typeof theme.palette, - string - ]; - const paletteKey = theme.palette[key] as unknown as Record; - const backgroundColor = paletteKey[shade]; - - return { Icon, backgroundColor }; - }, [level, theme]); + const Icon = LEVEL_ICONS[level]; + const palettePath = LEVEL_PALETTE_PATHS[level]; + const [paletteKeyName, shade] = palettePath.split('.') as [ + keyof typeof theme.palette, + string + ]; + const paletteKey = theme.palette[paletteKeyName] as unknown as Record< + string, + string + >; + const backgroundColor = paletteKey[shade]; return ( { const { setLocale, locale, LL } = useContext(I18nContext); - const onLocaleSelected: ChangeEventHandler = useCallback( - async ({ target }) => { - const loc = target.value as Locales; - localStorage.setItem('lang', loc); - await loadLocaleAsync(loc); - setLocale(loc); - }, - [setLocale] - ); - - // Memoize menu items to prevent recreation on every render - const menuItems = useMemo( - () => - LANGUAGE_OPTIONS.map(({ key, flag, label }) => ( - - {label} -  {label} - - )), - [] - ); + const onLocaleSelected: ChangeEventHandler = async ({ + target + }) => { + const loc = target.value as Locales; + localStorage.setItem('lang', loc); + await loadLocaleAsync(loc); + setLocale(loc); + }; return ( { size="small" select > - {menuItems} + {LANGUAGE_OPTIONS.map(({ key, flag, label }) => ( + + {label} +  {label} + + ))} ); }; diff --git a/interface/src/components/inputs/ValidatedPasswordField.tsx b/interface/src/components/inputs/ValidatedPasswordField.tsx index 44ab69995..5529e7d72 100644 --- a/interface/src/components/inputs/ValidatedPasswordField.tsx +++ b/interface/src/components/inputs/ValidatedPasswordField.tsx @@ -1,4 +1,4 @@ -import { memo, useCallback, useState } from 'react'; +import { memo, useState } from 'react'; import type { FC } from 'react'; import VisibilityIcon from '@mui/icons-material/Visibility'; @@ -13,9 +13,9 @@ type ValidatedPasswordFieldProps = Omit; const ValidatedPasswordField: FC = ({ ...props }) => { const [showPassword, setShowPassword] = useState(false); - const togglePasswordVisibility = useCallback(() => { + const togglePasswordVisibility = () => { setShowPassword((prev) => !prev); - }, []); + }; return ( = ({ children }) => { const [title, setTitle] = useState(PROJECT_NAME); const { pathname } = useLocation(); - // Memoize drawer toggle handler to prevent unnecessary re-renders const handleDrawerToggle = useCallback(() => { setMobileOpen((prev) => !prev); }, []); @@ -28,7 +27,6 @@ const LayoutComponent: FC = ({ children }) => { setMobileOpen(false); }, [pathname]); - // Memoize context value to prevent unnecessary re-renders const contextValue = useMemo(() => ({ title, setTitle }), [title]); return ( diff --git a/interface/src/components/layout/LayoutAppBar.tsx b/interface/src/components/layout/LayoutAppBar.tsx index 6bea11462..9ee84303a 100644 --- a/interface/src/components/layout/LayoutAppBar.tsx +++ b/interface/src/components/layout/LayoutAppBar.tsx @@ -1,4 +1,4 @@ -import { memo, useCallback, useMemo } from 'react'; +import { memo } from 'react'; import { Link, useLocation, useNavigate } from 'react-router'; import ArrowBackIcon from '@mui/icons-material/ArrowBack'; @@ -39,14 +39,11 @@ const LayoutAppBarComponent = ({ title, onToggleDrawer }: LayoutAppBarProps) => const navigate = useNavigate(); const location = useLocation(); - const pathnames = useMemo( - () => location.pathname.split('/').filter((x) => x), - [location.pathname] - ); + const pathnames = location.pathname.split('/').filter((x) => x); - const handleBackClick = useCallback(() => { + const handleBackClick = () => { void navigate('/' + pathnames[0]); - }, [navigate, pathnames]); + }; return ( diff --git a/interface/src/components/layout/LayoutDrawer.tsx b/interface/src/components/layout/LayoutDrawer.tsx index c89fb396b..89237615f 100644 --- a/interface/src/components/layout/LayoutDrawer.tsx +++ b/interface/src/components/layout/LayoutDrawer.tsx @@ -1,4 +1,4 @@ -import { memo, useMemo } from 'react'; +import { memo } from 'react'; import { Box, Divider, Drawer, Toolbar, Typography, styled } from '@mui/material'; @@ -24,22 +24,18 @@ interface LayoutDrawerProps { } const LayoutDrawerComponent = ({ mobileOpen, onClose }: LayoutDrawerProps) => { - // Memoize drawer content to prevent unnecessary re-renders - const drawer = useMemo( - () => ( - <> - - - - {PROJECT_NAME} - - - - - - - ), - [] + const drawer = ( + <> + + + + {PROJECT_NAME} + + + + + + ); return ( diff --git a/interface/src/components/layout/LayoutMenu.tsx b/interface/src/components/layout/LayoutMenu.tsx index 1fa3830b5..48ce3e626 100644 --- a/interface/src/components/layout/LayoutMenu.tsx +++ b/interface/src/components/layout/LayoutMenu.tsx @@ -1,4 +1,4 @@ -import { memo, useCallback, useContext, useState } from 'react'; +import { memo, useContext, useState } from 'react'; import AccountCircleIcon from '@mui/icons-material/AccountCircle'; import AssessmentIcon from '@mui/icons-material/Assessment'; @@ -22,9 +22,9 @@ const LayoutMenuComponent = () => { const { LL } = useI18nContext(); const [menuOpen, setMenuOpen] = useState(true); - const handleMenuToggle = useCallback(() => { + const handleMenuToggle = () => { setMenuOpen((prev) => !prev); - }, []); + }; return ( <> diff --git a/interface/src/components/layout/LayoutMenuItem.tsx b/interface/src/components/layout/LayoutMenuItem.tsx index a0dbc8354..2ec78433f 100644 --- a/interface/src/components/layout/LayoutMenuItem.tsx +++ b/interface/src/components/layout/LayoutMenuItem.tsx @@ -1,4 +1,4 @@ -import { memo, useMemo } from 'react'; +import { memo } from 'react'; import { Link, useLocation } from 'react-router'; import { ListItemButton, ListItemIcon, ListItemText } from '@mui/material'; @@ -21,50 +21,40 @@ const LayoutMenuItemComponent = ({ }: LayoutMenuItemProps) => { const { pathname } = useLocation(); - const selected = useMemo(() => routeMatches(to, pathname), [to, pathname]); + const selected = routeMatches(to, pathname); - // Memoize dynamic styles based on selected state - const buttonStyles: SxProps = useMemo( - () => ({ - transition: 'all 0.05s cubic-bezier(0.55, 0.085, 0.68, 0.53)', - backgroundColor: selected ? 'rgba(144, 202, 249, 0.1)' : 'transparent', - borderRadius: '8px', - margin: '2px 8px', - '&:hover': { - backgroundColor: 'rgba(68, 82, 211, 0.39)' - }, - '&::before': { - content: '""', - position: 'absolute', - left: 0, - top: 0, - bottom: 0, - width: selected ? '3px' : '0px', - backgroundColor: '#90caf9', - transition: 'width 0.05s cubic-bezier(0.55, 0.085, 0.68, 0.53)' - } - }), - [selected] - ); + const buttonStyles: SxProps = { + transition: 'all 0.05s cubic-bezier(0.55, 0.085, 0.68, 0.53)', + backgroundColor: selected ? 'rgba(144, 202, 249, 0.1)' : 'transparent', + borderRadius: '8px', + margin: '2px 8px', + '&:hover': { + backgroundColor: 'rgba(68, 82, 211, 0.39)' + }, + '&::before': { + content: '""', + position: 'absolute', + left: 0, + top: 0, + bottom: 0, + width: selected ? '3px' : '0px', + backgroundColor: '#90caf9', + transition: 'width 0.05s cubic-bezier(0.55, 0.085, 0.68, 0.53)' + } + }; - const iconStyles: SxProps = useMemo( - () => ({ - color: selected ? '#90caf9' : '#9e9e9e', - transition: 'color 0.05s cubic-bezier(0.55, 0.085, 0.68, 0.53)', - transform: selected ? 'scale(1.1)' : 'scale(1)', - transitionProperty: 'color, transform' - }), - [selected] - ); + const iconStyles: SxProps = { + color: selected ? '#90caf9' : '#9e9e9e', + transition: 'color 0.05s cubic-bezier(0.55, 0.085, 0.68, 0.53)', + transform: selected ? 'scale(1.1)' : 'scale(1)', + transitionProperty: 'color, transform' + }; - const textStyles: SxProps = useMemo( - () => ({ - color: selected ? '#90caf9' : '#f5f5f5', - transition: 'color 0.05s cubic-bezier(0.55, 0.085, 0.68, 0.53)', - transitionProperty: 'color, font-weight' - }), - [selected] - ); + const textStyles: SxProps = { + color: selected ? '#90caf9' : '#f5f5f5', + transition: 'color 0.05s cubic-bezier(0.55, 0.085, 0.68, 0.53)', + transitionProperty: 'color, font-weight' + }; return ( { const { LL } = useI18nContext(); - const handleReset = useCallback(() => { + const handleReset = () => { blocker.reset?.(); - }, [blocker]); + }; - const handleProceed = useCallback(() => { + const handleProceed = () => { blocker.proceed?.(); - }, [blocker]); + }; return ( diff --git a/interface/src/components/routing/RouterTabs.tsx b/interface/src/components/routing/RouterTabs.tsx index 9a3d7d31e..f8c6b2a09 100644 --- a/interface/src/components/routing/RouterTabs.tsx +++ b/interface/src/components/routing/RouterTabs.tsx @@ -1,4 +1,4 @@ -import { memo, useCallback } from 'react'; +import { memo } from 'react'; import type { FC } from 'react'; import { useNavigate } from 'react-router'; @@ -16,12 +16,9 @@ const RouterTabs: FC = ({ value, children }) => { const theme = useTheme(); const smallDown = useMediaQuery(theme.breakpoints.down('sm')); - const handleTabChange = useCallback( - (_event: unknown, path: string) => { - void navigate(path); - }, - [navigate] - ); + const handleTabChange = (_event: unknown, path: string) => { + void navigate(path); + }; return ( = ({ children }) => { void refresh(); }, [refresh]); - // cache object to prevent re-renders const obj = useMemo( () => ({ signIn, diff --git a/interface/src/utils/usePersistState.ts b/interface/src/utils/usePersistState.ts index 2a627a852..43b80afc8 100644 --- a/interface/src/utils/usePersistState.ts +++ b/interface/src/utils/usePersistState.ts @@ -1,34 +1,27 @@ -import { useEffect, useMemo, useState } from 'react'; +import { useEffect, useState } from 'react'; export const usePersistState = ( initial_value: T, id: string ): [T, (new_state: T) => void] => { - // Set initial value - only computed once on mount - const _initial_value = useMemo(() => { + const [state, setState] = useState(() => { try { - const local_storage_value_str = localStorage.getItem(`state:${id}`); - // If there is a value stored in localStorage, use that - if (local_storage_value_str) { - return JSON.parse(local_storage_value_str) as T; + const stored = localStorage.getItem(`state:${id}`); + if (stored) { + return JSON.parse(stored) as T; } } catch (error) { - // If parsing fails, fall back to initial_value console.warn( `Failed to parse localStorage value for key "state:${id}"`, error ); } - // Otherwise use initial_value that was passed to the function return initial_value; - }, [id]); // initial_value intentionally omitted - only read on first mount - - const [state, setState] = useState(_initial_value); + }); useEffect(() => { try { - const state_str = JSON.stringify(state); - localStorage.setItem(`state:${id}`, state_str); + localStorage.setItem(`state:${id}`, JSON.stringify(state)); } catch (error) { console.warn( `Failed to save state to localStorage for key "state:${id}"`, From 6473c55317973179be5d04a4ffa45f83c3b7e116 Mon Sep 17 00:00:00 2001 From: proddy Date: Mon, 27 Apr 2026 18:11:48 +0200 Subject: [PATCH 22/48] don't force an update on each request --- src/web/WebStatusService.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/web/WebStatusService.cpp b/src/web/WebStatusService.cpp index 31583585c..bbd636b51 100644 --- a/src/web/WebStatusService.cpp +++ b/src/web/WebStatusService.cpp @@ -314,8 +314,6 @@ uint8_t WebStatusService::upgradeImportantMessages(std::string & version) { // returns the device's current version for dev and stable // The remote fetch runs from the main loop task via WebStatusService::loop() so that we never block the AsyncTCP callback void WebStatusService::getVersions(JsonObject root) { - schedule_versions_refresh(); // force a refresh - FirmwareVersion current_version(current_version_s); bool is_dev = current_version.prerelease().find("dev") != std::string::npos; From 6e76bcc9afd2d9a0a0200bd9b1d9d4fae416cc88 Mon Sep 17 00:00:00 2001 From: proddy Date: Mon, 27 Apr 2026 18:12:05 +0200 Subject: [PATCH 23/48] show badge if there is an update available, which is cached --- interface/src/app/settings/Settings.tsx | 74 +++++++++++++- interface/src/app/status/Status.tsx | 97 +----------------- interface/src/app/status/Version.tsx | 98 ++++++++----------- .../src/components/layout/LayoutMenu.tsx | 5 +- .../src/components/layout/LayoutMenuItem.tsx | 20 +++- .../src/components/layout/ListMenuItem.tsx | 36 ++++++- .../authentication/Authentication.tsx | 32 +++++- .../src/contexts/authentication/context.ts | 4 +- interface/src/types/index.ts | 1 + interface/src/types/versions.ts | 23 +++++ mock-api/restServer.ts | 18 +++- 11 files changed, 240 insertions(+), 168 deletions(-) create mode 100644 interface/src/types/versions.ts diff --git a/interface/src/app/settings/Settings.tsx b/interface/src/app/settings/Settings.tsx index 7a31f66e7..9aab496b7 100644 --- a/interface/src/app/settings/Settings.tsx +++ b/interface/src/app/settings/Settings.tsx @@ -1,10 +1,13 @@ -import { useState } from 'react'; +import { useContext, useState } from 'react'; +import { toast } from 'react-toastify'; import AccessTimeIcon from '@mui/icons-material/AccessTime'; +import BuildIcon from '@mui/icons-material/Build'; import CancelIcon from '@mui/icons-material/Cancel'; import DeviceHubIcon from '@mui/icons-material/DeviceHub'; import ImportExportIcon from '@mui/icons-material/ImportExport'; import LockIcon from '@mui/icons-material/Lock'; +import PowerSettingsNewIcon from '@mui/icons-material/PowerSettingsNew'; import SettingsBackupRestoreIcon from '@mui/icons-material/SettingsBackupRestore'; import SettingsEthernetIcon from '@mui/icons-material/SettingsEthernet'; import SettingsInputAntennaIcon from '@mui/icons-material/SettingsInputAntenna'; @@ -28,15 +31,23 @@ import { useRequest } from 'alova/client'; import type { APIcall } from 'app/main/types'; import { SectionContent, useLayoutTitle } from 'components'; import ListMenuItem from 'components/layout/ListMenuItem'; +import { AuthenticatedContext } from 'contexts/authentication'; import { useI18nContext } from 'i18n/i18n-react'; import SystemMonitor from '../status/SystemMonitor'; const Settings = () => { const { LL } = useI18nContext(); + const { versions } = useContext(AuthenticatedContext); useLayoutTitle(LL.SETTINGS(0)); + const firmwareText = versions?.current?.version + ? `v${versions.current.version}` + : ''; + const upgradeAvailable = versions?.current?.upgradeable ?? false; + const [confirmFactoryReset, setConfirmFactoryReset] = useState(false); + const [confirmRestart, setConfirmRestart] = useState(false); const [restarting, setRestarting] = useState(); const { send: sendAPI } = useRequest((data: APIcall) => API(data), { @@ -50,6 +61,16 @@ const Settings = () => { }); }; + const doRestart = async () => { + setConfirmRestart(false); + setRestarting(true); + await sendAPI({ device: 'system', cmd: 'restart', id: 0 }).catch( + (error: Error) => { + toast.error(error.message); + } + ); + }; + const handleFactoryResetClose = () => { setConfirmFactoryReset(false); }; @@ -58,6 +79,14 @@ const Settings = () => { setConfirmFactoryReset(true); }; + const handleRestartClose = () => { + setConfirmRestart(false); + }; + + const handleRestartClick = () => { + setConfirmRestart(true); + }; + if (restarting) { return ; } @@ -65,6 +94,15 @@ const Settings = () => { return ( + + { + + {LL.RESTART()} + {LL.RESTART_CONFIRM()} + + + + + + { display: 'flex', justifyContent: 'flex-end', flexWrap: 'nowrap', - whiteSpace: 'nowrap' + whiteSpace: 'nowrap', + gap: 1 }} > + - )} { to="/status/log" /> - - - {LL.RESTART()} - {LL.RESTART_CONFIRM()} - - - - -
); }; diff --git a/interface/src/app/status/Version.tsx b/interface/src/app/status/Version.tsx index a41539053..490566587 100644 --- a/interface/src/app/status/Version.tsx +++ b/interface/src/app/status/Version.tsx @@ -1,4 +1,4 @@ -import { memo, useContext, useState } from 'react'; +import { memo, useContext, useMemo, useState } from 'react'; import { Link } from 'react-router'; import { toast } from 'react-toastify'; @@ -40,6 +40,7 @@ import { import { AuthenticatedContext } from 'contexts/authentication'; import { useI18nContext } from 'i18n/i18n-react'; import type { TranslationFunctions } from 'i18n/i18n-types'; +import type { VersionInfo } from 'types'; import { prettyDateTime } from 'utils/time'; // Constants moved outside component to avoid recreation @@ -70,26 +71,6 @@ interface VersionData { developer_mode: boolean; } -interface VersionInfo { - version: string; - date: string; -} - -interface RemoteVersionInfo extends VersionInfo { - upgradeable: boolean; -} - -interface CurrentVersionInfo extends VersionInfo { - type: 'stable' | 'dev'; -} - -// Response payload from the `getVersions` action -interface VersionsResponse { - current: CurrentVersionInfo; - stable?: RemoteVersionInfo; - dev?: RemoteVersionInfo; -} - // Memoized components for better performance const VersionInfoDialog = memo( ({ @@ -432,10 +413,7 @@ const getPlatform = (data: VersionData): string => { const Version = () => { const { LL, locale } = useI18nContext(); - const { me } = useContext(AuthenticatedContext); - - const [latestVersion, setLatestVersion] = useState(); - const [latestDevVersion, setLatestDevVersion] = useState(); + const { me, versions } = useContext(AuthenticatedContext); const [restarting, setRestarting] = useState(false); const [openInstallDialog, setOpenInstallDialog] = useState(false); @@ -447,16 +425,30 @@ const Version = () => { const [openInstallPartitionDialog, setOpenInstallPartitionDialog] = useState(false); - const [usingDevVersion, setUsingDevVersion] = useState(false); const [fetchDevVersion, setFetchDevVersion] = useState(false); - const [devUpgradeAvailable, setDevUpgradeAvailable] = useState(false); - const [stableUpgradeAvailable, setStableUpgradeAvailable] = - useState(false); - const [internetLive, setInternetLive] = useState(false); const [downloadOnly, setDownloadOnly] = useState(false); const [showVersionInfo, setShowVersionInfo] = useState(0); // 1 = stable, 2 = dev, 3 = partition const [firmwareSize, setFirmwareSize] = useState(0); + const latestVersion = useMemo( + () => + versions?.stable + ? { version: versions.stable.version, date: versions.stable.date } + : undefined, + [versions?.stable] + ); + const latestDevVersion = useMemo( + () => + versions?.dev + ? { version: versions.dev.version, date: versions.dev.date } + : undefined, + [versions?.dev] + ); + const usingDevVersion = versions?.current?.type === 'dev'; + const stableUpgradeAvailable = versions?.stable?.upgradeable ?? false; + const devUpgradeAvailable = versions?.dev?.upgradeable ?? false; + const internetLive = Boolean(versions?.stable || versions?.dev); + const { send: sendSetPartition } = useRequest( (partition: string) => callAction({ action: 'setPartition', param: partition }), { immediate: false } @@ -480,32 +472,6 @@ const Version = () => { { immediate: false } ); - // fetch latest stable/dev versions via the device. The C++ code makes a call to emsesp.org/versions.json itself - // if the device has no internet, stable/dev are omitted and the internetLive flag is set to false - useRequest(() => callAction({ action: 'getVersions' })) - .onSuccess((event) => { - const versions = event.data as VersionsResponse; - setUsingDevVersion(versions.current?.type === 'dev'); - if (versions.stable) { - setLatestVersion({ - version: versions.stable.version, - date: versions.stable.date - }); - setStableUpgradeAvailable(versions.stable.upgradeable); - } - if (versions.dev) { - setLatestDevVersion({ - version: versions.dev.version, - date: versions.dev.date - }); - setDevUpgradeAvailable(versions.dev.upgradeable); - } - setInternetLive(Boolean(versions.stable || versions.dev)); - }) - .onError(() => { - setInternetLive(false); - }); - const { send: sendAPI } = useRequest((data: APIcall) => API(data), { immediate: false }); @@ -640,15 +606,33 @@ const Version = () => { if (!me.admin) return null; + const isUpdateAvailable = choice === LL.UPDATE_AVAILABLE(); + return ( ); }; diff --git a/interface/src/components/layout/LayoutMenu.tsx b/interface/src/components/layout/LayoutMenu.tsx index 48ce3e626..5d52921a6 100644 --- a/interface/src/components/layout/LayoutMenu.tsx +++ b/interface/src/components/layout/LayoutMenu.tsx @@ -18,10 +18,12 @@ import { AuthenticatedContext } from 'contexts/authentication'; import { useI18nContext } from 'i18n/i18n-react'; const LayoutMenuComponent = () => { - const { me } = useContext(AuthenticatedContext); + const { me, versions } = useContext(AuthenticatedContext); const { LL } = useI18nContext(); const [menuOpen, setMenuOpen] = useState(true); + const upgradeAvailable = versions?.current?.upgradeable ?? false; + const handleMenuToggle = () => { setMenuOpen((prev) => !prev); }; @@ -105,6 +107,7 @@ const LayoutMenuComponent = () => { label={LL.SETTINGS(0)} disabled={!me.admin} to="/settings" + badge={upgradeAvailable} /> diff --git a/interface/src/components/layout/LayoutMenuItem.tsx b/interface/src/components/layout/LayoutMenuItem.tsx index 2ec78433f..5cdbf8c58 100644 --- a/interface/src/components/layout/LayoutMenuItem.tsx +++ b/interface/src/components/layout/LayoutMenuItem.tsx @@ -1,7 +1,7 @@ import { memo } from 'react'; import { Link, useLocation } from 'react-router'; -import { ListItemButton, ListItemIcon, ListItemText } from '@mui/material'; +import { Box, ListItemButton, ListItemIcon, ListItemText } from '@mui/material'; import type { SvgIconProps, SxProps, Theme } from '@mui/material'; import { routeMatches } from 'utils'; @@ -11,13 +11,15 @@ interface LayoutMenuItemProps { label: string; to: string; disabled?: boolean; + badge?: boolean; } const LayoutMenuItemComponent = ({ icon: Icon, label, to, - disabled + disabled, + badge }: LayoutMenuItemProps) => { const { pathname } = useLocation(); @@ -68,6 +70,20 @@ const LayoutMenuItemComponent = ({ {label} + {badge && ( + + )} ); }; diff --git a/interface/src/components/layout/ListMenuItem.tsx b/interface/src/components/layout/ListMenuItem.tsx index db92093de..c78d02ac2 100644 --- a/interface/src/components/layout/ListMenuItem.tsx +++ b/interface/src/components/layout/ListMenuItem.tsx @@ -5,6 +5,7 @@ import { Link } from 'react-router'; import NavigateNextIcon from '@mui/icons-material/NavigateNext'; import { Avatar, + Box, ListItem, ListItemAvatar, ListItemButton, @@ -20,6 +21,7 @@ interface ListMenuItemProps { text: string; to?: string; disabled?: boolean; + badge?: boolean; } const iconStyles: CSSProperties = { @@ -28,15 +30,40 @@ const iconStyles: CSSProperties = { verticalAlign: 'middle' }; +const Badge = () => ( + +); + const RenderIcon = memo( - ({ icon: Icon, bgcolor, label, text }: ListMenuItemProps) => ( + ({ icon: Icon, bgcolor, label, text, badge }: ListMenuItemProps) => ( <> - + + {label} + {badge && } + + } + secondary={text} + /> ) ); @@ -47,7 +74,8 @@ const LayoutMenuItem = ({ label, text, to, - disabled + disabled, + badge }: ListMenuItemProps) => ( <> {to && !disabled ? ( @@ -65,6 +93,7 @@ const LayoutMenuItem = ({ {...(bgcolor && { bgcolor })} label={label} text={text} + {...(badge && { badge })} /> @@ -75,6 +104,7 @@ const LayoutMenuItem = ({ {...(bgcolor && { bgcolor })} label={label} text={text} + {...(badge && { badge })} /> )} diff --git a/interface/src/contexts/authentication/Authentication.tsx b/interface/src/contexts/authentication/Authentication.tsx index 84b41e701..669f06e6a 100644 --- a/interface/src/contexts/authentication/Authentication.tsx +++ b/interface/src/contexts/authentication/Authentication.tsx @@ -3,6 +3,7 @@ import type { FC } from 'react'; import { redirect } from 'react-router'; import { toast } from 'react-toastify'; +import { callAction } from 'api/app'; import { ACCESS_TOKEN } from 'api/endpoints'; import * as AuthenticationApi from 'components/routing/authentication'; @@ -10,7 +11,7 @@ import { useRequest } from 'alova/client'; import { LoadingSpinner } from 'components'; import { verifyAuthorization } from 'components/routing/authentication'; import { useI18nContext } from 'i18n/i18n-react'; -import type { Me } from 'types'; +import type { Me, VersionsResponse } from 'types'; import type { RequiredChildrenProps } from 'utils'; import { AuthenticationContext } from './context'; @@ -20,17 +21,34 @@ const Authentication: FC = ({ children }) => { const [initialized, setInitialized] = useState(false); const [me, setMe] = useState(); + const [versions, setVersions] = useState(); const { send: sendVerifyAuthorization } = useRequest(verifyAuthorization(), { immediate: false }); + const { send: sendGetVersions } = useRequest( + () => callAction({ action: 'getVersions' }), + { immediate: false } + ) + .onSuccess((event) => { + setVersions(event.data as VersionsResponse); + }) + .onError(() => { + setVersions(undefined); + }); + + const refreshVersions = useCallback(async () => { + await sendGetVersions().catch(() => undefined); + }, []); + const signIn = (accessToken: string) => { try { AuthenticationApi.getStorage().setItem(ACCESS_TOKEN, accessToken); const decodedMe = AuthenticationApi.decodeMeJWT(accessToken); setMe(decodedMe); toast.success(LL.LOGGED_IN({ name: decodedMe.username })); + void refreshVersions(); } catch { setMe(undefined); throw new Error('Failed to parse JWT'); @@ -40,6 +58,7 @@ const Authentication: FC = ({ children }) => { const signOut = (doRedirect: boolean) => { AuthenticationApi.clearAccessToken(); setMe(undefined); + setVersions(undefined); if (doRedirect) { redirect('/'); } @@ -49,8 +68,9 @@ const Authentication: FC = ({ children }) => { const accessToken = AuthenticationApi.getStorage().getItem(ACCESS_TOKEN); if (accessToken) { await sendVerifyAuthorization() - .then(() => { + .then(async () => { setMe(AuthenticationApi.decodeMeJWT(accessToken)); + await refreshVersions(); setInitialized(true); }) .catch(() => { @@ -61,6 +81,8 @@ const Authentication: FC = ({ children }) => { setMe(undefined); setInitialized(true); } + // refreshVersions and sendVerifyAuthorization are stable + // eslint-disable-next-line react-hooks/exhaustive-deps }, []); useEffect(() => { @@ -72,9 +94,11 @@ const Authentication: FC = ({ children }) => { signIn, signOut, refresh, - ...(me && { me }) + refreshVersions, + ...(me && { me }), + ...(versions && { versions }) }), - [signIn, signOut, me, refresh] + [signIn, signOut, me, refresh, refreshVersions, versions] ); if (initialized) { diff --git a/interface/src/contexts/authentication/context.ts b/interface/src/contexts/authentication/context.ts index d58b320ff..1717bfaef 100644 --- a/interface/src/contexts/authentication/context.ts +++ b/interface/src/contexts/authentication/context.ts @@ -1,12 +1,14 @@ import { createContext } from 'react'; -import type { Me } from 'types'; +import type { Me, VersionsResponse } from 'types'; export interface AuthenticationContextValue { refresh: () => Promise; signIn: (accessToken: string) => void; signOut: (redirect: boolean) => void; me?: Me; + versions?: VersionsResponse; + refreshVersions: () => Promise; } const AuthenticationContextDefaultValue = {} as AuthenticationContextValue; diff --git a/interface/src/types/index.ts b/interface/src/types/index.ts index 8c2f8760c..a4e8726d9 100644 --- a/interface/src/types/index.ts +++ b/interface/src/types/index.ts @@ -7,3 +7,4 @@ export * from './ntp'; export * from './security'; export * from './signin'; export * from './system'; +export * from './versions'; diff --git a/interface/src/types/versions.ts b/interface/src/types/versions.ts new file mode 100644 index 000000000..6f081c613 --- /dev/null +++ b/interface/src/types/versions.ts @@ -0,0 +1,23 @@ +// Types for the `getVersions` action response coming from the device. +// The device proxies the request to emsesp.org/versions.json. If the device +// is offline the `stable` and `dev` fields are omitted. + +export interface VersionInfo { + version: string; + date: string; +} + +export interface RemoteVersionInfo extends VersionInfo { + upgradeable: boolean; +} + +export interface CurrentVersionInfo extends VersionInfo { + type: 'stable' | 'dev'; + upgradeable: boolean; +} + +export interface VersionsResponse { + current: CurrentVersionInfo; + stable?: RemoteVersionInfo; + dev?: RemoteVersionInfo; +} diff --git a/mock-api/restServer.ts b/mock-api/restServer.ts index f8cb3bd15..774b92543 100644 --- a/mock-api/restServer.ts +++ b/mock-api/restServer.ts @@ -144,10 +144,10 @@ let LATEST_STABLE_VERSION = '3.8.2'; let LATEST_DEV_VERSION = '3.8.3-dev.2'; // scenarios for testing versioning -let version_test = 0; // on latest stable, or switch to dev +// let version_test = 0; // on latest stable, or switch to dev // let version_test = 1; // on latest dev, or switch back to stable // let version_test = 2; // upgrade an older stable to latest stable or switch to latest dev -// let version_test = 3; // upgrade dev to latest, or switch to stable +let version_test = 3; // upgrade dev to latest, or switch to stable // let version_test = 4; // downgrade to an older dev, or switch back to stable switch (version_test as number) { @@ -419,15 +419,25 @@ function upgradeImportantMessages(version: string) { const MOCK_OFFLINE = false; function get_versions() { const isDev = THIS_VERSION.includes('dev'); + const currentUpgradeable = + !MOCK_OFFLINE && + (isDev ? DEV_VERSION_IS_UPGRADEABLE : STABLE_VERSION_IS_UPGRADEABLE); + const data: { - current: { version: string; type: 'stable' | 'dev'; date: string }; + current: { + version: string; + type: 'stable' | 'dev'; + date: string; + upgradeable: boolean; + }; stable?: { version: string; date: string; upgradeable: boolean }; dev?: { version: string; date: string; upgradeable: boolean }; } = { current: { version: THIS_VERSION, type: isDev ? 'dev' : 'stable', - date: '2026-04-25T12:00:00' + date: '2026-04-25T12:00:00', + upgradeable: currentUpgradeable } }; From b3a8737a71ae6cabebf409b5ad3f086cb1f345c9 Mon Sep 17 00:00:00 2001 From: proddy Date: Tue, 28 Apr 2026 16:27:39 +0200 Subject: [PATCH 24/48] move Version from status to settings --- interface/src/AuthenticatedRouting.tsx | 4 ++-- interface/src/app/settings/Settings.tsx | 8 ++++---- interface/src/app/{status => settings}/Version.tsx | 0 3 files changed, 6 insertions(+), 6 deletions(-) rename interface/src/app/{status => settings}/Version.tsx (100%) diff --git a/interface/src/AuthenticatedRouting.tsx b/interface/src/AuthenticatedRouting.tsx index 8ace232aa..1bab479ae 100644 --- a/interface/src/AuthenticatedRouting.tsx +++ b/interface/src/AuthenticatedRouting.tsx @@ -16,6 +16,7 @@ import DownloadUpload from 'app/settings/DownloadUpload'; import MqttSettings from 'app/settings/MqttSettings'; import NTPSettings from 'app/settings/NTPSettings'; import Settings from 'app/settings/Settings'; +import Version from 'app/settings/Version'; import Network from 'app/settings/network/Network'; import Security from 'app/settings/security/Security'; import APStatus from 'app/status/APStatus'; @@ -26,7 +27,6 @@ import NTPStatus from 'app/status/NTPStatus'; import NetworkStatus from 'app/status/NetworkStatus'; import Status from 'app/status/Status'; import SystemLog from 'app/status/SystemLog'; -import Version from 'app/status/Version'; import { Layout } from 'components'; import { AuthenticatedContext } from 'contexts/authentication'; @@ -49,11 +49,11 @@ const AuthenticatedRouting = memo(() => { } /> } /> } /> - } /> {me.admin && ( <> } /> + } /> } /> } /> } /> diff --git a/interface/src/app/settings/Settings.tsx b/interface/src/app/settings/Settings.tsx index 9aab496b7..85d66f39a 100644 --- a/interface/src/app/settings/Settings.tsx +++ b/interface/src/app/settings/Settings.tsx @@ -41,10 +41,10 @@ const Settings = () => { const { versions } = useContext(AuthenticatedContext); useLayoutTitle(LL.SETTINGS(0)); - const firmwareText = versions?.current?.version - ? `v${versions.current.version}` - : ''; const upgradeAvailable = versions?.current?.upgradeable ?? false; + const firmwareText = versions?.current?.version + ? `v${versions.current.version}${upgradeAvailable ? ` (${LL.UPDATE_AVAILABLE()})` : ''}` + : ''; const [confirmFactoryReset, setConfirmFactoryReset] = useState(false); const [confirmRestart, setConfirmRestart] = useState(false); @@ -99,7 +99,7 @@ const Settings = () => { bgcolor="#72caf9" label="EMS-ESP Firmware" text={firmwareText} - to="/status/version" + to="/settings/version" badge={upgradeAvailable} /> diff --git a/interface/src/app/status/Version.tsx b/interface/src/app/settings/Version.tsx similarity index 100% rename from interface/src/app/status/Version.tsx rename to interface/src/app/settings/Version.tsx From a3f0faf02272d4c2a76cb367227e81c5edc280b1 Mon Sep 17 00:00:00 2001 From: proddy Date: Tue, 28 Apr 2026 16:27:46 +0200 Subject: [PATCH 25/48] package update --- interface/package.json | 6 +- interface/pnpm-lock.yaml | 128 +++++++++++++++++++-------------------- 2 files changed, 67 insertions(+), 67 deletions(-) diff --git a/interface/package.json b/interface/package.json index 8bace3c1d..bff1f9aaf 100644 --- a/interface/package.json +++ b/interface/package.json @@ -3,7 +3,7 @@ "version": "3.8.2", "description": "EMS-ESP WebUI", "homepage": "https://emsesp.org", - "author": "proddy, emsesp.org", + "author": "emsesp.org", "license": "MIT", "private": true, "type": "module", @@ -46,17 +46,17 @@ "devDependencies": { "@eslint/js": "^10.0.1", "@preact/preset-vite": "^2.10.5", + "@trivago/prettier-plugin-sort-imports": "^6.0.2", "@types/node": "^25.6.0", "@types/react": "^19.2.14", "@types/react-dom": "^19.2.3", "concurrently": "^9.2.1", - "@trivago/prettier-plugin-sort-imports": "^6.0.2", "eslint": "^10.2.1", "eslint-config-prettier": "^10.1.8", "prettier": "^3.8.3", "rollup-plugin-visualizer": "^7.0.1", "terser": "^5.46.2", - "typescript-eslint": "^8.59.0", + "typescript-eslint": "^8.59.1", "vite": "^8.0.10", "vite-plugin-imagemin": "^0.6.1" }, diff --git a/interface/pnpm-lock.yaml b/interface/pnpm-lock.yaml index e2032919d..91ccc1fac 100644 --- a/interface/pnpm-lock.yaml +++ b/interface/pnpm-lock.yaml @@ -103,8 +103,8 @@ importers: specifier: ^5.46.2 version: 5.46.2 typescript-eslint: - specifier: ^8.59.0 - version: 8.59.0(eslint@10.2.1)(typescript@6.0.3) + specifier: ^8.59.1 + version: 8.59.1(eslint@10.2.1)(typescript@6.0.3) vite: specifier: ^8.0.10 version: 8.0.10(@types/node@25.6.0)(esbuild@0.27.4)(terser@5.46.2) @@ -1010,63 +1010,63 @@ packages: '@types/svgo@2.6.4': resolution: {integrity: sha512-l4cmyPEckf8moNYHdJ+4wkHvFxjyW6ulm9l4YGaOxeyBWPhBOT0gvni1InpFPdzx1dKf/2s62qGITwxNWnPQng==} - '@typescript-eslint/eslint-plugin@8.59.0': - resolution: {integrity: sha512-HyAZtpdkgZwpq8Sz3FSUvCR4c+ScbuWa9AksK2Jweub7w4M3yTz4O11AqVJzLYjy/B9ZWPyc81I+mOdJU/bDQw==} + '@typescript-eslint/eslint-plugin@8.59.1': + resolution: {integrity: sha512-BOziFIfE+6osHO9FoJG4zjoHUcvI7fTNBSpdAwrNH0/TLvzjsk2oo8XSSOT2HhqUyhZPfHv4UOffoJ9oEEQ7Ag==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - '@typescript-eslint/parser': ^8.59.0 + '@typescript-eslint/parser': ^8.59.1 eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/parser@8.59.0': - resolution: {integrity: sha512-TI1XGwKbDpo9tRW8UDIXCOeLk55qe9ZFGs8MTKU6/M08HWTw52DD/IYhfQtOEhEdPhLMT26Ka/x7p70nd3dzDg==} + '@typescript-eslint/parser@8.59.1': + resolution: {integrity: sha512-HDQH9O/47Dxi1ceDhBXdaldtf/WV9yRYMjbjCuNk3qnaTD564qwv61Y7+gTxwxRKzSrgO5uhtw584igXVuuZkA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/project-service@8.59.0': - resolution: {integrity: sha512-Lw5ITrR5s5TbC19YSvlr63ZfLaJoU6vtKTHyB0GQOpX0W7d5/Ir6vUahWi/8Sps/nOukZQ0IB3SmlxZnjaKVnw==} + '@typescript-eslint/project-service@8.59.1': + resolution: {integrity: sha512-+MuHQlHiEr00Of/IQbE/MmEoi44znZHbR/Pz7Opq4HryUOlRi+/44dro9Ycy8Fyo+/024IWtw8m4JUMCGTYxDg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/scope-manager@8.59.0': - resolution: {integrity: sha512-UzR16Ut8IpA3Mc4DbgAShlPPkVm8xXMWafXxB0BocaVRHs8ZGakAxGRskF7FId3sdk9lgGD73GSFaWmWFDE4dg==} + '@typescript-eslint/scope-manager@8.59.1': + resolution: {integrity: sha512-LwuHQI4pDOYVKvmH2dkaJo6YZCSgouVgnS/z7yBPKBMvgtBvyLqiLy9Z6b7+m/TRcX1NFYUqZetI5Y+aT4GEfg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/tsconfig-utils@8.59.0': - resolution: {integrity: sha512-91Sbl3s4Kb3SybliIY6muFBmHVv+pYXfybC4Oolp3dvk8BvIE3wOPc+403CWIT7mJNkfQRGtdqghzs2+Z91Tqg==} + '@typescript-eslint/tsconfig-utils@8.59.1': + resolution: {integrity: sha512-/0nEyPbX7gRsk0Uwfe4ALwwgxuA66d/l2mhRDNlAvaj4U3juhUtJNq0DsY8M2AYwwb9rEq2hrC3IcIcEt++iJA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/type-utils@8.59.0': - resolution: {integrity: sha512-3TRiZaQSltGqGeNrJzzr1+8YcEobKH9rHnqIp/1psfKFmhRQDNMGP5hBufanYTGznwShzVLs3Mz+gDN7HkWfXg==} + '@typescript-eslint/type-utils@8.59.1': + resolution: {integrity: sha512-klWPBR2ciQHS3f++ug/mVnWKPjBUo7icEL3FAO1lhAR1Z1i5NQYZ1EannMSRYcq5qCv5wNALlXr6fksRHyYl7w==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/types@8.59.0': - resolution: {integrity: sha512-nLzdsT1gdOgFxxxwrlNVUBzSNBEEHJ86bblmk4QAS6stfig7rcJzWKqCyxFy3YRRHXDWEkb2NralA1nOYkkm/A==} + '@typescript-eslint/types@8.59.1': + resolution: {integrity: sha512-ZDCjgccSdYPw5Bxh+my4Z0lJU96ZDN7jbBzvmEn0FZx3RtU1C7VWl6NbDx94bwY3V5YsgwRzJPOgeY2Q/nLG8A==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/typescript-estree@8.59.0': - resolution: {integrity: sha512-O9Re9P1BmBLFJyikRbQpLku/QA3/AueZNO9WePLBwQrvkixTmDe8u76B6CYUAITRl/rHawggEqUGn5QIkVRLMw==} + '@typescript-eslint/typescript-estree@8.59.1': + resolution: {integrity: sha512-OUd+vJS05sSkOip+BkZ/2NS8RMxrAAJemsC6vU3kmfLyeaJT0TftHkV9mcx2107MmsBVXXexhVu4F0TZXyMl4g==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/utils@8.59.0': - resolution: {integrity: sha512-I1R/K7V07XsMJ12Oaxg/O9GfrysGTmCRhvZJBv0RE0NcULMzjqVpR5kRRQjHsz3J/bElU7HwCO7zkqL+MSUz+g==} + '@typescript-eslint/utils@8.59.1': + resolution: {integrity: sha512-3pIeoXhCeYH9FSCBI8P3iNwJlGuzPlYKkTlen2O9T1DSeeg8UG8jstq6BLk+Mda0qup7mgk4z4XL4OzRaxZ8LA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/visitor-keys@8.59.0': - resolution: {integrity: sha512-/uejZt4dSere1bx12WLlPfv8GktzcaDtuJ7s42/HEZ5zGj9oxRaD4bj7qwSunXkf+pbAhFt2zjpHYUiT5lHf0Q==} + '@typescript-eslint/visitor-keys@8.59.1': + resolution: {integrity: sha512-LdDNl6C5iJExcM0Yh0PwAIBb9PrSiCsWamF/JyEZawm3kFDnRoaq3LGE4bpyRao/fWeGKKyw7icx0YxrLFC5Cg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} acorn-jsx@5.3.2: @@ -3153,8 +3153,8 @@ packages: peerDependencies: typescript: '>=3.5.1' - typescript-eslint@8.59.0: - resolution: {integrity: sha512-BU3ONW9X+v90EcCH9ZS6LMackcVtxRLlI3XrYyqZIwVSHIk7Qf7bFw1z0M9Q0IUxhTMZCf8piY9hTYaNEIASrw==} + typescript-eslint@8.59.1: + resolution: {integrity: sha512-xqDcFVBmlrltH64lklOVp1wYxgJr6LVdg3NamBgH2OOQDLFdTKfIZXF5PfghrnXQKXZGTQs8tr1vL7fJvq8CTQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 @@ -4124,14 +4124,14 @@ snapshots: dependencies: '@types/node': 25.6.0 - '@typescript-eslint/eslint-plugin@8.59.0(@typescript-eslint/parser@8.59.0(eslint@10.2.1)(typescript@6.0.3))(eslint@10.2.1)(typescript@6.0.3)': + '@typescript-eslint/eslint-plugin@8.59.1(@typescript-eslint/parser@8.59.1(eslint@10.2.1)(typescript@6.0.3))(eslint@10.2.1)(typescript@6.0.3)': dependencies: '@eslint-community/regexpp': 4.12.2 - '@typescript-eslint/parser': 8.59.0(eslint@10.2.1)(typescript@6.0.3) - '@typescript-eslint/scope-manager': 8.59.0 - '@typescript-eslint/type-utils': 8.59.0(eslint@10.2.1)(typescript@6.0.3) - '@typescript-eslint/utils': 8.59.0(eslint@10.2.1)(typescript@6.0.3) - '@typescript-eslint/visitor-keys': 8.59.0 + '@typescript-eslint/parser': 8.59.1(eslint@10.2.1)(typescript@6.0.3) + '@typescript-eslint/scope-manager': 8.59.1 + '@typescript-eslint/type-utils': 8.59.1(eslint@10.2.1)(typescript@6.0.3) + '@typescript-eslint/utils': 8.59.1(eslint@10.2.1)(typescript@6.0.3) + '@typescript-eslint/visitor-keys': 8.59.1 eslint: 10.2.1 ignore: 7.0.5 natural-compare: 1.4.0 @@ -4140,41 +4140,41 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/parser@8.59.0(eslint@10.2.1)(typescript@6.0.3)': + '@typescript-eslint/parser@8.59.1(eslint@10.2.1)(typescript@6.0.3)': dependencies: - '@typescript-eslint/scope-manager': 8.59.0 - '@typescript-eslint/types': 8.59.0 - '@typescript-eslint/typescript-estree': 8.59.0(typescript@6.0.3) - '@typescript-eslint/visitor-keys': 8.59.0 + '@typescript-eslint/scope-manager': 8.59.1 + '@typescript-eslint/types': 8.59.1 + '@typescript-eslint/typescript-estree': 8.59.1(typescript@6.0.3) + '@typescript-eslint/visitor-keys': 8.59.1 debug: 4.4.3 eslint: 10.2.1 typescript: 6.0.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/project-service@8.59.0(typescript@6.0.3)': + '@typescript-eslint/project-service@8.59.1(typescript@6.0.3)': dependencies: - '@typescript-eslint/tsconfig-utils': 8.59.0(typescript@6.0.3) - '@typescript-eslint/types': 8.59.0 + '@typescript-eslint/tsconfig-utils': 8.59.1(typescript@6.0.3) + '@typescript-eslint/types': 8.59.1 debug: 4.4.3 typescript: 6.0.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/scope-manager@8.59.0': + '@typescript-eslint/scope-manager@8.59.1': dependencies: - '@typescript-eslint/types': 8.59.0 - '@typescript-eslint/visitor-keys': 8.59.0 + '@typescript-eslint/types': 8.59.1 + '@typescript-eslint/visitor-keys': 8.59.1 - '@typescript-eslint/tsconfig-utils@8.59.0(typescript@6.0.3)': + '@typescript-eslint/tsconfig-utils@8.59.1(typescript@6.0.3)': dependencies: typescript: 6.0.3 - '@typescript-eslint/type-utils@8.59.0(eslint@10.2.1)(typescript@6.0.3)': + '@typescript-eslint/type-utils@8.59.1(eslint@10.2.1)(typescript@6.0.3)': dependencies: - '@typescript-eslint/types': 8.59.0 - '@typescript-eslint/typescript-estree': 8.59.0(typescript@6.0.3) - '@typescript-eslint/utils': 8.59.0(eslint@10.2.1)(typescript@6.0.3) + '@typescript-eslint/types': 8.59.1 + '@typescript-eslint/typescript-estree': 8.59.1(typescript@6.0.3) + '@typescript-eslint/utils': 8.59.1(eslint@10.2.1)(typescript@6.0.3) debug: 4.4.3 eslint: 10.2.1 ts-api-utils: 2.5.0(typescript@6.0.3) @@ -4182,14 +4182,14 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/types@8.59.0': {} + '@typescript-eslint/types@8.59.1': {} - '@typescript-eslint/typescript-estree@8.59.0(typescript@6.0.3)': + '@typescript-eslint/typescript-estree@8.59.1(typescript@6.0.3)': dependencies: - '@typescript-eslint/project-service': 8.59.0(typescript@6.0.3) - '@typescript-eslint/tsconfig-utils': 8.59.0(typescript@6.0.3) - '@typescript-eslint/types': 8.59.0 - '@typescript-eslint/visitor-keys': 8.59.0 + '@typescript-eslint/project-service': 8.59.1(typescript@6.0.3) + '@typescript-eslint/tsconfig-utils': 8.59.1(typescript@6.0.3) + '@typescript-eslint/types': 8.59.1 + '@typescript-eslint/visitor-keys': 8.59.1 debug: 4.4.3 minimatch: 10.2.5 semver: 7.7.4 @@ -4199,20 +4199,20 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/utils@8.59.0(eslint@10.2.1)(typescript@6.0.3)': + '@typescript-eslint/utils@8.59.1(eslint@10.2.1)(typescript@6.0.3)': dependencies: '@eslint-community/eslint-utils': 4.9.1(eslint@10.2.1) - '@typescript-eslint/scope-manager': 8.59.0 - '@typescript-eslint/types': 8.59.0 - '@typescript-eslint/typescript-estree': 8.59.0(typescript@6.0.3) + '@typescript-eslint/scope-manager': 8.59.1 + '@typescript-eslint/types': 8.59.1 + '@typescript-eslint/typescript-estree': 8.59.1(typescript@6.0.3) eslint: 10.2.1 typescript: 6.0.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/visitor-keys@8.59.0': + '@typescript-eslint/visitor-keys@8.59.1': dependencies: - '@typescript-eslint/types': 8.59.0 + '@typescript-eslint/types': 8.59.1 eslint-visitor-keys: 5.0.1 acorn-jsx@5.3.2(acorn@8.16.0): @@ -6333,12 +6333,12 @@ snapshots: dependencies: typescript: 6.0.3 - typescript-eslint@8.59.0(eslint@10.2.1)(typescript@6.0.3): + typescript-eslint@8.59.1(eslint@10.2.1)(typescript@6.0.3): dependencies: - '@typescript-eslint/eslint-plugin': 8.59.0(@typescript-eslint/parser@8.59.0(eslint@10.2.1)(typescript@6.0.3))(eslint@10.2.1)(typescript@6.0.3) - '@typescript-eslint/parser': 8.59.0(eslint@10.2.1)(typescript@6.0.3) - '@typescript-eslint/typescript-estree': 8.59.0(typescript@6.0.3) - '@typescript-eslint/utils': 8.59.0(eslint@10.2.1)(typescript@6.0.3) + '@typescript-eslint/eslint-plugin': 8.59.1(@typescript-eslint/parser@8.59.1(eslint@10.2.1)(typescript@6.0.3))(eslint@10.2.1)(typescript@6.0.3) + '@typescript-eslint/parser': 8.59.1(eslint@10.2.1)(typescript@6.0.3) + '@typescript-eslint/typescript-estree': 8.59.1(typescript@6.0.3) + '@typescript-eslint/utils': 8.59.1(eslint@10.2.1)(typescript@6.0.3) eslint: 10.2.1 typescript: 6.0.3 transitivePeerDependencies: From 381fcf4080bfed40e1e8786b11cdf7517a063f14 Mon Sep 17 00:00:00 2001 From: proddy Date: Tue, 28 Apr 2026 16:27:59 +0200 Subject: [PATCH 26/48] ESP32Async/ESPAsyncWebServer @ 3.11.0 --- platformio.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/platformio.ini b/platformio.ini index c94e1383f..63ba90a67 100644 --- a/platformio.ini +++ b/platformio.ini @@ -105,7 +105,7 @@ board_build.filesystem = littlefs lib_deps = bblanchon/ArduinoJson @ 7.4.3 ESP32Async/AsyncTCP @ 3.4.10 - ESP32Async/ESPAsyncWebServer @ 3.10.3 + ESP32Async/ESPAsyncWebServer @ 3.11.0 ; https://github.com/emsesp/EMS-ESP-Modules.git @ 1.0.8 ; builds the web interface only, not the firmware From 53ac82520e920dda251746627ba662776ab1492f Mon Sep 17 00:00:00 2001 From: proddy Date: Tue, 28 Apr 2026 16:28:11 +0200 Subject: [PATCH 27/48] DeserializationError is enum --- src/web/WebStatusService.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/web/WebStatusService.cpp b/src/web/WebStatusService.cpp index bbd636b51..43800486e 100644 --- a/src/web/WebStatusService.cpp +++ b/src/web/WebStatusService.cpp @@ -428,7 +428,7 @@ bool WebStatusService::refresh_versions_cache() { http.end(); if (err) { #if defined(EMSESP_DEBUG) - EMSESP::logger().debug("versions.json: parse error (%s)", err.c_str()); + EMSESP::logger().debug("versions.json: parse error"); #endif return false; } From 3b765b308ef16977f8a0df386840cd20b2826c35 Mon Sep 17 00:00:00 2001 From: proddy Date: Tue, 28 Apr 2026 17:31:50 +0200 Subject: [PATCH 28/48] remove unused useMemo --- .../src/app/main/CustomEntitiesDialog.tsx | 15 ++--- interface/src/app/main/DevicesDialog.tsx | 5 +- interface/src/app/main/SchedulerDialog.tsx | 15 ++--- .../src/app/main/SensorsAnalogDialog.tsx | 21 +++---- .../src/app/main/SensorsTemperatureDialog.tsx | 19 +++---- .../src/app/settings/security/ManageUsers.tsx | 28 ++++------ interface/src/utils/useRest.ts | 55 +++++++------------ 7 files changed, 58 insertions(+), 100 deletions(-) diff --git a/interface/src/app/main/CustomEntitiesDialog.tsx b/interface/src/app/main/CustomEntitiesDialog.tsx index 3a2cf945f..8ede016b4 100644 --- a/interface/src/app/main/CustomEntitiesDialog.tsx +++ b/interface/src/app/main/CustomEntitiesDialog.tsx @@ -1,4 +1,4 @@ -import { useEffect, useMemo, useState } from 'react'; +import { useEffect, useState } from 'react'; import AddIcon from '@mui/icons-material/Add'; import CancelIcon from '@mui/icons-material/Cancel'; @@ -68,15 +68,10 @@ const CustomEntitiesDialog = ({ const { LL } = useI18nContext(); const [editItem, setEditItem] = useState(selectedItem); const [fieldErrors, setFieldErrors] = useState(); - // Stable handler reference so the memoized ValidatedTextField can skip re-renders - const updateFormValue = useMemo( - () => - updateValue( - setEditItem as unknown as React.Dispatch< - React.SetStateAction> - > - ), - [] + const updateFormValue = updateValue( + setEditItem as unknown as React.Dispatch< + React.SetStateAction> + > ); useEffect(() => { diff --git a/interface/src/app/main/DevicesDialog.tsx b/interface/src/app/main/DevicesDialog.tsx index 89a6fae13..706c2d608 100644 --- a/interface/src/app/main/DevicesDialog.tsx +++ b/interface/src/app/main/DevicesDialog.tsx @@ -1,4 +1,4 @@ -import { useEffect, useMemo, useState } from 'react'; +import { useEffect, useState } from 'react'; import CancelIcon from '@mui/icons-material/Cancel'; import WarningIcon from '@mui/icons-material/Warning'; @@ -52,8 +52,7 @@ const DevicesDialog = ({ const [editItem, setEditItem] = useState(selectedItem); const [fieldErrors, setFieldErrors] = useState(); - // Stable handler reference so the memoized ValidatedTextField can skip re-renders - const updateFormValue = useMemo(() => updateValue(setEditItem), [setEditItem]); + const updateFormValue = updateValue(setEditItem); useEffect(() => { if (open) { diff --git a/interface/src/app/main/SchedulerDialog.tsx b/interface/src/app/main/SchedulerDialog.tsx index ca3ff97dd..d68d9b727 100644 --- a/interface/src/app/main/SchedulerDialog.tsx +++ b/interface/src/app/main/SchedulerDialog.tsx @@ -1,4 +1,4 @@ -import { useEffect, useMemo, useState } from 'react'; +import { useEffect, useState } from 'react'; import AddIcon from '@mui/icons-material/Add'; import CancelIcon from '@mui/icons-material/Cancel'; @@ -90,15 +90,10 @@ const SchedulerDialog = ({ const [fieldErrors, setFieldErrors] = useState(); const [scheduleType, setScheduleType] = useState(); - // Stable handler reference so the memoized ValidatedTextField can skip re-renders - const updateFormValue = useMemo( - () => - updateValue( - setEditItem as unknown as React.Dispatch< - React.SetStateAction> - > - ), - [] + const updateFormValue = updateValue( + setEditItem as unknown as React.Dispatch< + React.SetStateAction> + > ); useEffect(() => { diff --git a/interface/src/app/main/SensorsAnalogDialog.tsx b/interface/src/app/main/SensorsAnalogDialog.tsx index 283a00803..483d337cc 100644 --- a/interface/src/app/main/SensorsAnalogDialog.tsx +++ b/interface/src/app/main/SensorsAnalogDialog.tsx @@ -1,4 +1,4 @@ -import { useEffect, useMemo, useState } from 'react'; +import { useEffect, useState } from 'react'; import CancelIcon from '@mui/icons-material/Cancel'; import DoneIcon from '@mui/icons-material/Done'; @@ -53,18 +53,13 @@ const SensorsAnalogDialog = ({ const [fieldErrors, setFieldErrors] = useState(); const [editItem, setEditItem] = useState(selectedItem); - // Stable handler reference so the memoized ValidatedTextField can skip re-renders - const updateFormValue = useMemo( - () => - updateValue((updater) => - setEditItem( - (prev) => - updater( - prev as unknown as Record - ) as unknown as AnalogSensor - ) - ), - [setEditItem] + const updateFormValue = updateValue((updater) => + setEditItem( + (prev) => + updater( + prev as unknown as Record + ) as unknown as AnalogSensor + ) ); const isCounterOrRate = diff --git a/interface/src/app/main/SensorsTemperatureDialog.tsx b/interface/src/app/main/SensorsTemperatureDialog.tsx index 21a422a77..714156a85 100644 --- a/interface/src/app/main/SensorsTemperatureDialog.tsx +++ b/interface/src/app/main/SensorsTemperatureDialog.tsx @@ -1,4 +1,4 @@ -import { useEffect, useMemo, useState } from 'react'; +import { useEffect, useState } from 'react'; import CancelIcon from '@mui/icons-material/Cancel'; import DoneIcon from '@mui/icons-material/Done'; @@ -50,17 +50,12 @@ const SensorsTemperatureDialog = ({ const [fieldErrors, setFieldErrors] = useState(); const [editItem, setEditItem] = useState(selectedItem); - // Stable handler reference so the memoized ValidatedTextField can skip re-renders - const updateFormValue = useMemo( - () => - updateValue( - setEditItem as unknown as ( - updater: ( - prevState: Readonly> - ) => Record - ) => void - ), - [setEditItem] + const updateFormValue = updateValue( + setEditItem as unknown as ( + updater: ( + prevState: Readonly> + ) => Record + ) => void ); useEffect(() => { diff --git a/interface/src/app/settings/security/ManageUsers.tsx b/interface/src/app/settings/security/ManageUsers.tsx index 9dec88ee9..5ae9de30a 100644 --- a/interface/src/app/settings/security/ManageUsers.tsx +++ b/interface/src/app/settings/security/ManageUsers.tsx @@ -1,4 +1,4 @@ -import { memo, useCallback, useContext, useMemo, useState } from 'react'; +import { memo, useCallback, useContext, useState } from 'react'; import { useBlocker } from 'react-router'; import CancelIcon from '@mui/icons-material/Cancel'; @@ -55,16 +55,14 @@ const ManageUsers = () => { const blocker = useBlocker(changed !== 0); const { LL } = useI18nContext(); - const table_theme = useMemo( - () => - useTheme({ - Table: ` + const table_theme = useTheme({ + Table: ` --data-table-library_grid-template-columns: repeat(1, minmax(0, 1fr)) minmax(120px, max-content) 120px; `, - BaseRow: ` + BaseRow: ` font-size: 14px; `, - HeaderRow: ` + HeaderRow: ` text-transform: uppercase; background-color: black; color: #90CAF9; @@ -74,7 +72,7 @@ const ManageUsers = () => { border-bottom: 1px solid #565656; } `, - Row: ` + Row: ` .td { padding: 8px; border-top: 1px solid #565656; @@ -87,7 +85,7 @@ const ManageUsers = () => { background-color: #1e1e1e; } `, - BaseCell: ` + BaseCell: ` &:nth-of-type(2) { text-align: center; } @@ -95,9 +93,7 @@ const ManageUsers = () => { text-align: right; } ` - }), - [] - ); + }); const noAdminConfigured = () => !data?.users.find((u) => u.admin); @@ -122,11 +118,11 @@ const ManageUsers = () => { setUser({ ...toEdit }); }; - const cancelEditingUser = useCallback(() => { + const cancelEditingUser = () => { setUser(undefined); - }, []); + }; - const doneEditingUser = useCallback(() => { + const doneEditingUser = () => { if (user && data) { const users = [ ...data.users.filter( @@ -138,7 +134,7 @@ const ManageUsers = () => { setUser(undefined); setChanged(changed + 1); } - }, [user, data, updateDataValue, changed]); + }; const closeGenerateToken = useCallback(() => { setGeneratingToken(undefined); diff --git a/interface/src/utils/useRest.ts b/interface/src/utils/useRest.ts index 0d57abff9..791c6e4e0 100644 --- a/interface/src/utils/useRest.ts +++ b/interface/src/utils/useRest.ts @@ -1,4 +1,4 @@ -import { useCallback, useMemo, useState } from 'react'; +import { useCallback, useState } from 'react'; import { useBlocker } from 'react-router'; import { toast } from 'react-toastify'; @@ -54,61 +54,44 @@ export const useRest = ({ read, update }: RestRequestOptions) => { } }, [readData]); - const saveData = useCallback(async () => { + const saveData = async () => { if (!data) return; - // Reset states before saving setRestartNeeded(false); setErrorMessage(undefined); try { await writeData(data as D); - // Only update origData on successful save (dirtyFlags cleared by onSuccess handler) setOrigData(data as D); } catch (error) { const message = error instanceof Error ? error.message : String(error); if (message === REBOOT_ERROR_MESSAGE) { setRestartNeeded(true); - return; // Early return - save succeeded but needs reboot + return; } - // Restore original data on validation error if (origData) { updateData({ data: origData }); } toast.error(message); setErrorMessage(message); - setDirtyFlags([]); // Clear flags so user can retry + setDirtyFlags([]); } - }, [data, writeData, origData, updateData]); + }; - return useMemo( - () => ({ - loadData, - saveData, - saving: !!saving, - updateDataValue, - data: data as D, - origData: origData as D, - dirtyFlags, - setDirtyFlags, - setOrigData, - blocker, - errorMessage, - restartNeeded - }), - [ - loadData, - saveData, - saving, - updateDataValue, - data, - origData, - dirtyFlags, - blocker, - errorMessage, - restartNeeded - ] - ); + return { + loadData, + saveData, + saving: !!saving, + updateDataValue, + data: data as D, + origData: origData as D, + dirtyFlags, + setDirtyFlags, + setOrigData, + blocker, + errorMessage, + restartNeeded + }; }; From 2cbb5ec5f201de1d17daf1519af75ccfc2b848a6 Mon Sep 17 00:00:00 2001 From: proddy Date: Tue, 28 Apr 2026 20:09:22 +0200 Subject: [PATCH 29/48] move restart button from Settings to Version page. only show Factory Reset when in developer mode --- interface/src/app/settings/Settings.tsx | 149 +----------------------- interface/src/app/settings/Version.tsx | 108 +++++++++++++++++ mock-api/restServer.ts | 3 +- 3 files changed, 112 insertions(+), 148 deletions(-) diff --git a/interface/src/app/settings/Settings.tsx b/interface/src/app/settings/Settings.tsx index 85d66f39a..7808633d4 100644 --- a/interface/src/app/settings/Settings.tsx +++ b/interface/src/app/settings/Settings.tsx @@ -1,41 +1,21 @@ -import { useContext, useState } from 'react'; -import { toast } from 'react-toastify'; +import { useContext } from 'react'; import AccessTimeIcon from '@mui/icons-material/AccessTime'; import BuildIcon from '@mui/icons-material/Build'; -import CancelIcon from '@mui/icons-material/Cancel'; import DeviceHubIcon from '@mui/icons-material/DeviceHub'; import ImportExportIcon from '@mui/icons-material/ImportExport'; import LockIcon from '@mui/icons-material/Lock'; -import PowerSettingsNewIcon from '@mui/icons-material/PowerSettingsNew'; -import SettingsBackupRestoreIcon from '@mui/icons-material/SettingsBackupRestore'; import SettingsEthernetIcon from '@mui/icons-material/SettingsEthernet'; import SettingsInputAntennaIcon from '@mui/icons-material/SettingsInputAntenna'; import TuneIcon from '@mui/icons-material/Tune'; import ViewModuleIcon from '@mui/icons-material/ViewModule'; -import { - Box, - Button, - Dialog, - DialogActions, - DialogContent, - DialogTitle, - Divider, - List -} from '@mui/material'; +import { List } from '@mui/material'; -import { API } from 'api/app'; - -import { dialogStyle } from 'CustomTheme'; -import { useRequest } from 'alova/client'; -import type { APIcall } from 'app/main/types'; import { SectionContent, useLayoutTitle } from 'components'; import ListMenuItem from 'components/layout/ListMenuItem'; import { AuthenticatedContext } from 'contexts/authentication'; import { useI18nContext } from 'i18n/i18n-react'; -import SystemMonitor from '../status/SystemMonitor'; - const Settings = () => { const { LL } = useI18nContext(); const { versions } = useContext(AuthenticatedContext); @@ -46,51 +26,6 @@ const Settings = () => { ? `v${versions.current.version}${upgradeAvailable ? ` (${LL.UPDATE_AVAILABLE()})` : ''}` : ''; - const [confirmFactoryReset, setConfirmFactoryReset] = useState(false); - const [confirmRestart, setConfirmRestart] = useState(false); - const [restarting, setRestarting] = useState(); - - const { send: sendAPI } = useRequest((data: APIcall) => API(data), { - immediate: false - }); - - const doFormat = async () => { - await sendAPI({ device: 'system', cmd: 'format', id: 0 }).then(() => { - setRestarting(true); - setConfirmFactoryReset(false); - }); - }; - - const doRestart = async () => { - setConfirmRestart(false); - setRestarting(true); - await sendAPI({ device: 'system', cmd: 'restart', id: 0 }).catch( - (error: Error) => { - toast.error(error.message); - } - ); - }; - - const handleFactoryResetClose = () => { - setConfirmFactoryReset(false); - }; - - const handleFactoryResetClick = () => { - setConfirmFactoryReset(true); - }; - - const handleRestartClose = () => { - setConfirmRestart(false); - }; - - const handleRestartClick = () => { - setConfirmRestart(true); - }; - - if (restarting) { - return ; - } - return ( @@ -166,86 +101,6 @@ const Settings = () => { to="downloadUpload" /> - - - {LL.FACTORY_RESET()} - {LL.SYSTEM_FACTORY_TEXT_DIALOG()} - - - - - - - - {LL.RESTART()} - {LL.RESTART_CONFIRM()} - - - - - - - - - - - - ); }; diff --git a/interface/src/app/settings/Version.tsx b/interface/src/app/settings/Version.tsx index 490566587..ceaff599b 100644 --- a/interface/src/app/settings/Version.tsx +++ b/interface/src/app/settings/Version.tsx @@ -7,6 +7,8 @@ import CloseIcon from '@mui/icons-material/Close'; import CheckIcon from '@mui/icons-material/Done'; import DownloadIcon from '@mui/icons-material/GetApp'; import InfoOutlinedIcon from '@mui/icons-material/InfoOutlined'; +import PowerSettingsNewIcon from '@mui/icons-material/PowerSettingsNew'; +import SettingsBackupRestoreIcon from '@mui/icons-material/SettingsBackupRestore'; import WarningIcon from '@mui/icons-material/Warning'; import { Box, @@ -15,6 +17,7 @@ import { DialogActions, DialogContent, DialogTitle, + Divider, Grid, IconButton, Table, @@ -416,6 +419,8 @@ const Version = () => { const { me, versions } = useContext(AuthenticatedContext); const [restarting, setRestarting] = useState(false); + const [confirmFactoryReset, setConfirmFactoryReset] = useState(false); + const [confirmRestart, setConfirmRestart] = useState(false); const [openInstallDialog, setOpenInstallDialog] = useState(false); const [partitionVersion, setPartitionVersion] = useState( @@ -515,6 +520,7 @@ const Version = () => { }; const doRestart = async () => { + setConfirmRestart(false); await sendAPI({ device: 'system', cmd: 'restart', id: 0 }).catch( (error: Error) => { toast.error(error.message); @@ -523,6 +529,18 @@ const Version = () => { setRestarting(true); }; + const doFormat = async () => { + await sendAPI({ device: 'system', cmd: 'format', id: 0 }).then(() => { + setRestarting(true); + setConfirmFactoryReset(false); + }); + }; + + const handleFactoryResetClose = () => setConfirmFactoryReset(false); + const handleFactoryResetClick = () => setConfirmFactoryReset(true); + const handleRestartClose = () => setConfirmRestart(false); + const handleRestartClick = () => setConfirmRestart(true); + const installFirmwareURL = async (url: string) => { await sendUploadURL(url).catch((error: Error) => { toast.error(error.message); @@ -846,6 +864,96 @@ const Version = () => { )} + + {me.admin && ( + <> + + {LL.FACTORY_RESET()} + {LL.SYSTEM_FACTORY_TEXT_DIALOG()} + + + + + + + + {LL.RESTART()} + {LL.RESTART_CONFIRM()} + + + + + + + {/* */} + + + + {data.developer_mode && ( + + )} + + + )}
); }; diff --git a/mock-api/restServer.ts b/mock-api/restServer.ts index 774b92543..1291bc023 100644 --- a/mock-api/restServer.ts +++ b/mock-api/restServer.ts @@ -127,7 +127,7 @@ let system_status = { } ], // partitions: [], - developer_mode: true, + developer_mode: settings.developer_mode, model: '', board: '', // model: 'BBQKees Electronics EMS Gateway E32 V2 (E32 V2.0 P3/2024011)', @@ -4602,6 +4602,7 @@ router .post(EMSESP_SETTINGS_ENDPOINT, async (request: any) => { settings = await request.json(); console.log('application settings saved', settings); + system_status.developer_mode = settings.developer_mode; return status(200); // no restart needed // return status(205); // reboot required }) From 4d3408254ef23b887a356b540d75004ccc738500 Mon Sep 17 00:00:00 2001 From: proddy Date: Wed, 29 Apr 2026 08:57:12 +0200 Subject: [PATCH 30/48] chore: stop tracking .vscode/settings.json Already listed in .gitignore but was tracked, so local edits kept showing up as pending changes. Untrack it so the ignore rule applies. Made-with: Cursor --- .vscode/settings.json | 101 ------------------------------------------ 1 file changed, 101 deletions(-) delete mode 100644 .vscode/settings.json diff --git a/.vscode/settings.json b/.vscode/settings.json deleted file mode 100644 index 1893589ab..000000000 --- a/.vscode/settings.json +++ /dev/null @@ -1,101 +0,0 @@ -{ - "search.exclude": { - "**/.yarn": true, - "**/.pnp.*": true - }, - "editor.codeActionsOnSave": { - "source.fixAll": "explicit" - }, - "eslint.validate": [ - "typescript" - ], - "eslint.codeActionsOnSave.rules": null, - "eslint.nodePath": "interface/.yarn/sdks", - "eslint.workingDirectories": ["interface"], - "prettier.prettierPath": "", - "typescript.enablePromptUseWorkspaceTsdk": true, - "files.associations": { - "*.tsx": "typescriptreact", - "*.tcc": "cpp", - "optional": "cpp", - "istream": "cpp", - "ostream": "cpp", - "ratio": "cpp", - "system_error": "cpp", - "array": "cpp", - "functional": "cpp", - "regex": "cpp", - "tuple": "cpp", - "type_traits": "cpp", - "utility": "cpp", - "string": "cpp", - "string_view": "cpp", - "atomic": "cpp", - "bitset": "cpp", - "cctype": "cpp", - "chrono": "cpp", - "clocale": "cpp", - "cmath": "cpp", - "condition_variable": "cpp", - "cstdarg": "cpp", - "cstddef": "cpp", - "cstdint": "cpp", - "cstdio": "cpp", - "cstdlib": "cpp", - "cstring": "cpp", - "ctime": "cpp", - "cwchar": "cpp", - "cwctype": "cpp", - "deque": "cpp", - "list": "cpp", - "unordered_map": "cpp", - "unordered_set": "cpp", - "vector": "cpp", - "exception": "cpp", - "algorithm": "cpp", - "iterator": "cpp", - "map": "cpp", - "memory": "cpp", - "memory_resource": "cpp", - "numeric": "cpp", - "random": "cpp", - "set": "cpp", - "fstream": "cpp", - "initializer_list": "cpp", - "iomanip": "cpp", - "iosfwd": "cpp", - "iostream": "cpp", - "limits": "cpp", - "mutex": "cpp", - "new": "cpp", - "sstream": "cpp", - "stdexcept": "cpp", - "streambuf": "cpp", - "thread": "cpp", - "cinttypes": "cpp", - "typeinfo": "cpp" - }, - "todo-tree.filtering.excludeGlobs": [ - "**/vendor/**", - "**/node_modules/**", - "**/dist/**", - "**/bower_components/**", - "**/build/**", - "**/.vscode/**", - "**/.github/**", - "**/_output/**", - "**/*.min.*", - "**/*.map", - "**/ArduinoJson/**" - ], - "cSpell.enableFiletypes": [ - "ini", - "makefile" - ], - "typescript.preferences.preferTypeOnlyAutoImports": true, - "sonarlint.pathToCompileCommands": "${workspaceFolder}/compile_commands.json", - "sonarlint.connectedMode.project": { - "connectionId": "emsesp", - "projectKey": "emsesp_EMS-ESP32" - } - } \ No newline at end of file From fd5a39702b50033b570036cc2b0157f3179c3d37 Mon Sep 17 00:00:00 2001 From: proddy Date: Wed, 29 Apr 2026 08:57:46 +0200 Subject: [PATCH 31/48] update --- interface/pnpm-lock.yaml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/interface/pnpm-lock.yaml b/interface/pnpm-lock.yaml index 91ccc1fac..b50e3120b 100644 --- a/interface/pnpm-lock.yaml +++ b/interface/pnpm-lock.yaml @@ -1151,8 +1151,8 @@ packages: base64-js@1.5.1: resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} - baseline-browser-mapping@2.10.23: - resolution: {integrity: sha512-xwVXGqevyKPsiuQdLj+dZMVjidjJV508TBqexND5HrF89cGdCYCJFB3qhcxRHSeMctdCfbR1jrxBajhDy7o29g==} + baseline-browser-mapping@2.10.24: + resolution: {integrity: sha512-I2NkZOOrj2XuguvWCK6OVh9GavsNjZjK908Rq3mIBK25+GD8vPX5w2WdxVqnQ7xx3SrZJiCiZFu+/Oz50oSYSA==} engines: {node: '>=6.0.0'} hasBin: true @@ -4279,7 +4279,7 @@ snapshots: base64-js@1.5.1: {} - baseline-browser-mapping@2.10.23: {} + baseline-browser-mapping@2.10.24: {} bin-build@3.0.0: dependencies: @@ -4340,7 +4340,7 @@ snapshots: browserslist@4.28.2: dependencies: - baseline-browser-mapping: 2.10.23 + baseline-browser-mapping: 2.10.24 caniuse-lite: 1.0.30001791 electron-to-chromium: 1.5.344 node-releases: 2.0.38 From 41cd49a61c00eb81704aa39a5272bb74b7598474 Mon Sep 17 00:00:00 2001 From: proddy Date: Wed, 29 Apr 2026 08:57:53 +0200 Subject: [PATCH 32/48] remove comment --- interface/src/app/settings/Version.tsx | 2 -- 1 file changed, 2 deletions(-) diff --git a/interface/src/app/settings/Version.tsx b/interface/src/app/settings/Version.tsx index ceaff599b..373133a0a 100644 --- a/interface/src/app/settings/Version.tsx +++ b/interface/src/app/settings/Version.tsx @@ -921,8 +921,6 @@ const Version = () => { - {/* */} - Date: Wed, 29 Apr 2026 08:58:04 +0200 Subject: [PATCH 33/48] use 3.9.0 as dummy latest dev version --- mock-api/restServer.ts | 2 +- src/web/WebStatusService.cpp | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/mock-api/restServer.ts b/mock-api/restServer.ts index 1291bc023..22e2c72b3 100644 --- a/mock-api/restServer.ts +++ b/mock-api/restServer.ts @@ -141,7 +141,7 @@ let DEV_VERSION_IS_UPGRADEABLE: boolean; let STABLE_VERSION_IS_UPGRADEABLE: boolean; let THIS_VERSION: string; let LATEST_STABLE_VERSION = '3.8.2'; -let LATEST_DEV_VERSION = '3.8.3-dev.2'; +let LATEST_DEV_VERSION = '3.9.0-dev.1'; // scenarios for testing versioning // let version_test = 0; // on latest stable, or switch to dev diff --git a/src/web/WebStatusService.cpp b/src/web/WebStatusService.cpp index 43800486e..ee3cadb68 100644 --- a/src/web/WebStatusService.cpp +++ b/src/web/WebStatusService.cpp @@ -363,9 +363,9 @@ void WebStatusService::getVersions(JsonObject root) { stable_out["upgradeable"] = FirmwareVersion("3.8.2") > current_version; JsonObject dev_out = root["dev"].to(); - dev_out["version"] = "3.8.3-dev.2"; + dev_out["version"] = "3.9.0-dev.1"; dev_out["date"] = "2026-04-25"; - dev_out["upgradeable"] = FirmwareVersion("3.8.3-dev.2") > current_version; + dev_out["upgradeable"] = FirmwareVersion("3.9.0-dev.1") > current_version; #endif } From 751c540cb31f92f0a10675386932ac8c800a63c2 Mon Sep 17 00:00:00 2001 From: proddy Date: Fri, 1 May 2026 08:07:05 +0200 Subject: [PATCH 34/48] refactor network code --- Makefile | 2 +- interface/src/app/settings/APSettings.tsx | 4 - interface/src/i18n/cz/index.ts | 3 +- interface/src/i18n/de/index.ts | 3 +- interface/src/i18n/en/index.ts | 3 +- interface/src/i18n/fr/index.ts | 3 +- interface/src/i18n/it/index.ts | 3 +- interface/src/i18n/nl/index.ts | 3 +- interface/src/i18n/no/index.ts | 3 +- interface/src/i18n/pl/index.ts | 1 - interface/src/i18n/sk/index.ts | 3 +- interface/src/i18n/sv/index.ts | 3 +- interface/src/i18n/tr/index.ts | 3 +- interface/src/types/ap.ts | 1 - lib/uuid-syslog/src/syslog.cpp | 2 +- lib_standalone/ESP32React.h | 37 +- src/ESP32React/APSettingsService.cpp | 105 +--- src/ESP32React/APSettingsService.h | 24 +- src/ESP32React/APStatus.cpp | 8 +- src/ESP32React/APStatus.h | 5 - src/ESP32React/ESP32React.cpp | 2 - src/ESP32React/ESP32React.h | 13 - src/ESP32React/MqttSettingsService.cpp | 4 +- src/ESP32React/NTPSettingsService.cpp | 4 +- src/ESP32React/NetworkSettingsService.cpp | 474 +-------------- src/ESP32React/NetworkSettingsService.h | 53 -- src/ESP32React/NetworkStatus.cpp | 9 +- src/core/console.cpp | 15 +- src/core/emsesp.cpp | 11 +- src/core/emsesp.h | 2 + src/core/locale_common.h | 1 + src/core/mqtt.cpp | 4 +- src/core/network.cpp | 673 ++++++++++++++++++++++ src/core/network.h | 215 +++++++ src/core/system.cpp | 98 +--- src/core/system.h | 48 +- src/emsesp_version.h | 2 +- src/web/WebStatusService.cpp | 4 +- 38 files changed, 999 insertions(+), 852 deletions(-) create mode 100644 src/core/network.cpp create mode 100644 src/core/network.h diff --git a/Makefile b/Makefile index 28c81c2f8..ae75f3d73 100644 --- a/Makefile +++ b/Makefile @@ -113,7 +113,7 @@ CXX := /usr/bin/g++ CPPFLAGS += $(DEFINES) $(DEFAULTS) $(INCLUDE) CPPFLAGS += -ggdb -g3 -MMD CPPFLAGS += -flto=auto -CPPFLAGS += -Wall -Wextra -Werror -Wswitch-enum +CPPFLAGS += -Wall -Wextra -Werror -Wno-switch-enum CPPFLAGS += -Wno-unused-parameter -Wno-missing-braces -Wno-vla-cxx-extension CPPFLAGS += -ffunction-sections -fdata-sections -fno-exceptions -fno-rtti -fno-threadsafe-statics CPPFLAGS += -Os -DNDEBUG diff --git a/interface/src/app/settings/APSettings.tsx b/interface/src/app/settings/APSettings.tsx index 7d8c8875c..c4a20d42c 100644 --- a/interface/src/app/settings/APSettings.tsx +++ b/interface/src/app/settings/APSettings.tsx @@ -24,7 +24,6 @@ import { numberValue, updateValueDirty, useRest } from 'utils'; import { ValidationError, createAPSettingsValidator, validate } from 'validators'; export const isAPEnabled = ({ provision_mode }: APSettingsType) => - provision_mode === APProvisionMode.AP_MODE_ALWAYS || provision_mode === APProvisionMode.AP_MODE_DISCONNECTED; // Efficient range function without recursion @@ -108,9 +107,6 @@ const APSettings = () => { onChange={updateFormValue} margin="normal" > - - {LL.AP_PROVIDE_TEXT_1()} - {LL.AP_PROVIDE_TEXT_2()} diff --git a/interface/src/i18n/cz/index.ts b/interface/src/i18n/cz/index.ts index fbce09041..ef7726653 100644 --- a/interface/src/i18n/cz/index.ts +++ b/interface/src/i18n/cz/index.ts @@ -247,8 +247,7 @@ const cz: Translation = { TIME_ZONE: 'Časová zóna', ACCESS_POINT: 'Přístupový bod', AP_PROVIDE: 'Povolit přístupový bod', - AP_PROVIDE_TEXT_1: 'Vždy', - AP_PROVIDE_TEXT_2: 'Když je WiFi odpojena', + AP_PROVIDE_TEXT_2: 'Když je síťové připojení stratené', AP_PROVIDE_TEXT_3: 'Nikdy', AP_PREFERRED_CHANNEL: 'Preferovaný kanál', AP_HIDE_SSID: 'Skrýt SSID', diff --git a/interface/src/i18n/de/index.ts b/interface/src/i18n/de/index.ts index 3a766efb4..ee0988be4 100644 --- a/interface/src/i18n/de/index.ts +++ b/interface/src/i18n/de/index.ts @@ -247,8 +247,7 @@ const de: Translation = { TIME_ZONE: 'Zeitzone', ACCESS_POINT: 'Zugangspunkt', AP_PROVIDE: 'Aktiviere Zugangspunkt', - AP_PROVIDE_TEXT_1: 'Immer', - AP_PROVIDE_TEXT_2: 'Wenn WiFi nicht verbunden', + AP_PROVIDE_TEXT_2: 'Wenn Netzwerkverbindung verloren geht', AP_PROVIDE_TEXT_3: 'Niemals', AP_PREFERRED_CHANNEL: 'Bevorzugter Kanal', AP_HIDE_SSID: 'Verstecke SSID', diff --git a/interface/src/i18n/en/index.ts b/interface/src/i18n/en/index.ts index 610ed0f00..268b0ff64 100644 --- a/interface/src/i18n/en/index.ts +++ b/interface/src/i18n/en/index.ts @@ -247,8 +247,7 @@ const en: Translation = { TIME_ZONE: 'Time Zone', ACCESS_POINT: 'Access Point', AP_PROVIDE: 'Enable Access Point', - AP_PROVIDE_TEXT_1: 'Always', - AP_PROVIDE_TEXT_2: 'When WiFi is disconnected', + AP_PROVIDE_TEXT_2: 'When network connection is lost', AP_PROVIDE_TEXT_3: 'Never', AP_PREFERRED_CHANNEL: 'Preferred Channel', AP_HIDE_SSID: 'Hide SSID', diff --git a/interface/src/i18n/fr/index.ts b/interface/src/i18n/fr/index.ts index f1945d2ab..380d1a705 100644 --- a/interface/src/i18n/fr/index.ts +++ b/interface/src/i18n/fr/index.ts @@ -247,8 +247,7 @@ const fr: Translation = { TIME_ZONE: 'Fuseau horaire', ACCESS_POINT: "Point d'accès", AP_PROVIDE: "Activer le Point d'Accès", - AP_PROVIDE_TEXT_1: 'toujours', - AP_PROVIDE_TEXT_2: 'quand le WiFi est déconnecté', + AP_PROVIDE_TEXT_2: 'quand la connexion réseau est perdue', AP_PROVIDE_TEXT_3: 'jamais', AP_PREFERRED_CHANNEL: 'Canal préféré', AP_HIDE_SSID: 'Cacher le SSID', diff --git a/interface/src/i18n/it/index.ts b/interface/src/i18n/it/index.ts index df8b4de61..93b666c37 100644 --- a/interface/src/i18n/it/index.ts +++ b/interface/src/i18n/it/index.ts @@ -247,8 +247,7 @@ const it: Translation = { TIME_ZONE: 'Fuso orario', ACCESS_POINT: 'Access Point', AP_PROVIDE: 'Abilita Access Point', - AP_PROVIDE_TEXT_1: 'sempre', - AP_PROVIDE_TEXT_2: 'quando WiFi é disconnessa', + AP_PROVIDE_TEXT_2: 'quando la connessione di rete è persa', AP_PROVIDE_TEXT_3: 'mai', AP_PREFERRED_CHANNEL: 'Canale preferito', AP_HIDE_SSID: 'Nascondi SSID', diff --git a/interface/src/i18n/nl/index.ts b/interface/src/i18n/nl/index.ts index 888c033d8..d87348d58 100644 --- a/interface/src/i18n/nl/index.ts +++ b/interface/src/i18n/nl/index.ts @@ -247,8 +247,7 @@ const nl: Translation = { TIME_ZONE: 'Tijdzone', ACCESS_POINT: 'Access Point', AP_PROVIDE: 'Activeer Access Point', - AP_PROVIDE_TEXT_1: 'altijd', - AP_PROVIDE_TEXT_2: 'als WiFi niet is verbonden', + AP_PROVIDE_TEXT_2: 'als netwerk verbinding verloren gaat', AP_PROVIDE_TEXT_3: 'nooit', AP_PREFERRED_CHANNEL: 'Voorkeurskanaal', AP_HIDE_SSID: 'SSID verbergen', diff --git a/interface/src/i18n/no/index.ts b/interface/src/i18n/no/index.ts index 693ef29e8..841d32689 100644 --- a/interface/src/i18n/no/index.ts +++ b/interface/src/i18n/no/index.ts @@ -247,8 +247,7 @@ const no: Translation = { TIME_ZONE: 'Tidssone', ACCESS_POINT: 'Aksesspunkt', AP_PROVIDE: 'Aktiver Aksesspunkt', - AP_PROVIDE_TEXT_1: 'alltid', - AP_PROVIDE_TEXT_2: 'når WiFi er utilgjengelig', + AP_PROVIDE_TEXT_2: 'når nettverksforbindelsen er utilgjengelig', AP_PROVIDE_TEXT_3: 'aldri', AP_PREFERRED_CHANNEL: 'Foretrukket kanal', AP_HIDE_SSID: 'Skjul SSID', diff --git a/interface/src/i18n/pl/index.ts b/interface/src/i18n/pl/index.ts index c6dd004de..24ac4d078 100644 --- a/interface/src/i18n/pl/index.ts +++ b/interface/src/i18n/pl/index.ts @@ -247,7 +247,6 @@ const pl: BaseTranslation = { TIME_ZONE: 'Strefa czasowa', ACCESS_POINT: '{{Punkt|punktu|}} {{dostępowy|dostępowego|}}', AP_PROVIDE: 'Punkt dostępowy', - AP_PROVIDE_TEXT_1: 'zawsze aktywny', AP_PROVIDE_TEXT_2: 'aktywny jeśli brak połączenia z siecią', AP_PROVIDE_TEXT_3: 'nieaktywny', AP_PREFERRED_CHANNEL: 'Preferowany kanał', diff --git a/interface/src/i18n/sk/index.ts b/interface/src/i18n/sk/index.ts index 5e993e998..286eca096 100644 --- a/interface/src/i18n/sk/index.ts +++ b/interface/src/i18n/sk/index.ts @@ -247,8 +247,7 @@ const sk: Translation = { TIME_ZONE: 'Časová zóna', ACCESS_POINT: 'Prístupový bod', AP_PROVIDE: 'Povoliť prístupový bod', - AP_PROVIDE_TEXT_1: 'vždy', - AP_PROVIDE_TEXT_2: 'keď je WiFi odpojená', + AP_PROVIDE_TEXT_2: 'keď je sieťové pripojenie stratené', AP_PROVIDE_TEXT_3: 'nikdy', AP_PREFERRED_CHANNEL: 'Preferovaný kanál', AP_HIDE_SSID: 'Skryť SSID', diff --git a/interface/src/i18n/sv/index.ts b/interface/src/i18n/sv/index.ts index 032eeb026..43bc9e9a3 100644 --- a/interface/src/i18n/sv/index.ts +++ b/interface/src/i18n/sv/index.ts @@ -247,8 +247,7 @@ const sv: Translation = { TIME_ZONE: 'Tidszon', ACCESS_POINT: 'Accesspunkt', AP_PROVIDE: 'Aktivera accesspunkt', - AP_PROVIDE_TEXT_1: 'alltid', - AP_PROVIDE_TEXT_2: 'när WiFi är nedkopplat', + AP_PROVIDE_TEXT_2: 'när nätverksanslutningen är bortkopplad', AP_PROVIDE_TEXT_3: 'aldrig', AP_PREFERRED_CHANNEL: 'Kanal', AP_HIDE_SSID: 'Göm SSID', diff --git a/interface/src/i18n/tr/index.ts b/interface/src/i18n/tr/index.ts index b6caa3238..93867c4fb 100644 --- a/interface/src/i18n/tr/index.ts +++ b/interface/src/i18n/tr/index.ts @@ -247,8 +247,7 @@ const tr: Translation = { TIME_ZONE: 'Saat dilimi', ACCESS_POINT: 'Erişim Noktası', AP_PROVIDE: 'Erişim noktasını çalıştır', - AP_PROVIDE_TEXT_1: 'her zaman', - AP_PROVIDE_TEXT_2: 'Kablosuz bağlantı kesildiğinde', + AP_PROVIDE_TEXT_2: 'Ağ bağlantısı kesildiğinde', AP_PROVIDE_TEXT_3: 'asla', AP_PREFERRED_CHANNEL: 'Tercih edilen kanal', AP_HIDE_SSID: 'SSID yi gizle', diff --git a/interface/src/types/ap.ts b/interface/src/types/ap.ts index de5fe64ad..5094b0dbb 100644 --- a/interface/src/types/ap.ts +++ b/interface/src/types/ap.ts @@ -1,5 +1,4 @@ export enum APProvisionMode { - AP_MODE_ALWAYS = 0, AP_MODE_DISCONNECTED = 1, AP_NEVER = 2 } diff --git a/lib/uuid-syslog/src/syslog.cpp b/lib/uuid-syslog/src/syslog.cpp index 4e0eac774..0ebdaebeb 100644 --- a/lib/uuid-syslog/src/syslog.cpp +++ b/lib/uuid-syslog/src/syslog.cpp @@ -231,7 +231,7 @@ SyslogService::QueuedLogMessage::QueuedLogMessage(unsigned long id, std::shared_ : id_(id) , content_(std::move(content)) { // Added for EMS-ESP - if (time_good_ || emsesp::EMSESP::system_.network_connected()) { + if (time_good_ || emsesp::EMSESP::network_.network_connected()) { #if UUID_SYSLOG_HAVE_GETTIMEOFDAY if (gettimeofday(&time_, nullptr) != 0) { time_.tv_sec = (time_t)-1; diff --git a/lib_standalone/ESP32React.h b/lib_standalone/ESP32React.h index cb5644db6..f2026c479 100644 --- a/lib_standalone/ESP32React.h +++ b/lib_standalone/ESP32React.h @@ -12,6 +12,7 @@ #include "SecuritySettingsService.h" #include "StatefulService.h" #include "Network.h" +// #include "IPAddress.h" #include @@ -21,7 +22,6 @@ #define NTP_SETTINGS_FILE "/config/ntpSettings.json" #define EMSESP_SETTINGS_FILE "/config/emsespSettings.json" -#define AP_MODE_ALWAYS 0 class DummySettings { public: // SYSTEM @@ -49,6 +49,22 @@ class DummySettings { uint16_t keepAlive = 60; bool cleanSession = false; uint8_t entity_format = 1; + String CORSOrigin = "*"; + uint8_t tx_power = 0; + String bssid = ""; + String localIP = ""; + String gatewayIP = ""; + String subnetMask = ""; + bool staticIPConfig = false; + String dnsIP1 = ""; + String dnsIP2 = ""; + bool enableMDNS = true; + bool enableCORS = false; + uint8_t channel = 1; + bool ssid_hidden = false; + uint8_t max_clients = 4; + bool ssidHidden = false; + uint8_t maxClients = 4; uint16_t publish_time_boiler = 10; uint16_t publish_time_thermostat = 10; @@ -59,21 +75,10 @@ class DummySettings { uint16_t publish_time_heartbeat = 60; uint32_t publish_time_water = 0; - String hostname = "ems-esp"; - String jwtSecret = "ems-esp"; - String ssid = "ems-esp"; - String password = "ems-esp"; - String bssid = ""; - String localIP = ""; - String gatewayIP = ""; - String subnetMask = ""; - bool staticIPConfig = false; - String dnsIP1 = ""; - String dnsIP2 = ""; - bool enableMDNS = true; - bool enableCORS = false; - String CORSOrigin = "*"; - uint8_t tx_power = 0; + String hostname = "ems-esp"; + String jwtSecret = "ems-esp"; + String ssid = "ems-esp"; + String password = "ems-esp"; // AP uint8_t provisionMode = 0; diff --git a/src/ESP32React/APSettingsService.cpp b/src/ESP32React/APSettingsService.cpp index a68037738..05d02a110 100644 --- a/src/ESP32React/APSettingsService.cpp +++ b/src/ESP32React/APSettingsService.cpp @@ -4,110 +4,17 @@ APSettingsService::APSettingsService(AsyncWebServer * server, FS * fs, SecurityManager * securityManager) : _httpEndpoint(APSettings::read, APSettings::update, this, server, AP_SETTINGS_SERVICE_PATH, securityManager) - , _fsPersistence(APSettings::read, APSettings::update, this, fs, AP_SETTINGS_FILE) - , _dnsServer(nullptr) - , _lastManaged(0) - , _reconfigureAp(false) - , _connected(0) { - addUpdateHandler([this] { reconfigureAP(); }, false); + , _fsPersistence(APSettings::read, APSettings::update, this, fs, AP_SETTINGS_FILE) { } void APSettingsService::begin() { _fsPersistence.readFromFS(); - // disabled for delayed start, first try station mode - // reconfigureAP(); -} - -void APSettingsService::reconfigureAP() { - _lastManaged = uuid::get_uptime() - MANAGE_NETWORK_DELAY; - _reconfigureAp = true; -} - -void APSettingsService::loop() { - const uint8_t was_connected = _connected; - if (WiFi.isConnected()) { - _connected |= 1U; - } else { - _connected &= ~1U; - } - if (ETH.connected()) { - _connected |= 2U; - } else { - _connected &= ~2U; - } - // wait 10 sec before starting AP - if (was_connected && !_connected) { - _lastManaged = uuid::get_uptime(); - } - const unsigned long currentMillis = uuid::get_uptime(); - if ((currentMillis - _lastManaged) >= MANAGE_NETWORK_DELAY) { - _lastManaged = currentMillis; - manageAP(); - } - - if (_dnsServer) { - handleDNS(); - } -} - -void APSettingsService::manageAP() { - const WiFiMode_t currentWiFiMode = WiFi.getMode(); - if (_state.provisionMode == AP_MODE_ALWAYS || (_state.provisionMode == AP_MODE_DISCONNECTED && !_connected)) { - if (_reconfigureAp || currentWiFiMode == WIFI_OFF || currentWiFiMode == WIFI_STA) { - startAP(); - } - } else if ((currentWiFiMode == WIFI_AP || currentWiFiMode == WIFI_AP_STA) && _connected && (_reconfigureAp || !WiFi.softAPgetStationNum())) { - stopAP(); - } - _reconfigureAp = false; -} - -void APSettingsService::startAP() { - WiFi.softAPenableIPv6(); // force IPV6, same as for WiFi - fixes https://github.com/emsesp/EMS-ESP32/issues/1922 - WiFi.softAPConfig(_state.localIP, _state.gatewayIP, _state.subnetMask); - esp_wifi_set_bandwidth(static_cast(ESP_IF_WIFI_AP), WIFI_BW_HT20); - WiFi.softAP(_state.ssid.c_str(), _state.password.c_str(), _state.channel, _state.ssidHidden, _state.maxClients); -#if CONFIG_IDF_TARGET_ESP32C3 - WiFi.setTxPower(WIFI_POWER_8_5dBm); // https://www.wemos.cc/en/latest/c3/c3_mini_1_0_0.html#about-wifi -#endif - if (!_dnsServer) { - const IPAddress apIp = WiFi.softAPIP(); - char ipStr[16]; - snprintf(ipStr, sizeof(ipStr), "%u.%u.%u.%u", apIp[0], apIp[1], apIp[2], apIp[3]); - emsesp::EMSESP::logger().info("Starting Access Point with captive portal on %s", ipStr); - _dnsServer = new DNSServer; - _dnsServer->start(DNS_PORT, "*", apIp); - } -} - -void APSettingsService::stopAP() { - if (_dnsServer) { - emsesp::EMSESP::logger().info("Stopping Access Point"); - _dnsServer->stop(); - delete _dnsServer; - _dnsServer = nullptr; - } - WiFi.softAPdisconnect(true); -} - -void APSettingsService::handleDNS() { - if (_dnsServer) { - _dnsServer->processNextRequest(); - } } APNetworkStatus APSettingsService::getAPNetworkStatus() { - const WiFiMode_t currentWiFiMode = WiFi.getMode(); - const bool apActive = (currentWiFiMode == WIFI_AP || currentWiFiMode == WIFI_AP_STA); - - if (apActive && _state.provisionMode != AP_MODE_ALWAYS && WiFi.status() == WL_CONNECTED) { - return APNetworkStatus::LINGERING; - } - - return apActive ? APNetworkStatus::ACTIVE : APNetworkStatus::INACTIVE; + return emsesp::EMSESP::network_.ap_connected() ? APNetworkStatus::ACTIVE : APNetworkStatus::INACTIVE; } - void APSettings::read(const APSettings & settings, JsonObject root) { root["provision_mode"] = settings.provisionMode; root["ssid"] = settings.ssid; @@ -125,12 +32,11 @@ StateUpdateResult APSettings::update(JsonObject root, APSettings & settings) { newSettings.provisionMode = static_cast(root["provision_mode"] | FACTORY_AP_PROVISION_MODE); switch (settings.provisionMode) { - case AP_MODE_ALWAYS: case AP_MODE_DISCONNECTED: case AP_MODE_NEVER: break; default: - newSettings.provisionMode = AP_MODE_ALWAYS; + newSettings.provisionMode = AP_MODE_DISCONNECTED; } newSettings.ssid = root["ssid"] | FACTORY_AP_SSID; @@ -148,5 +54,10 @@ StateUpdateResult APSettings::update(JsonObject root, APSettings & settings) { } settings = newSettings; + + // if the AP mode has changed, force a disconnect and reconnect + if (settings.provisionMode != newSettings.provisionMode) { + emsesp::EMSESP::network_.reconnect(); + } return StateUpdateResult::CHANGED; } diff --git a/src/ESP32React/APSettingsService.h b/src/ESP32React/APSettingsService.h index 6051c5cc5..6c280875b 100644 --- a/src/ESP32React/APSettingsService.h +++ b/src/ESP32React/APSettingsService.h @@ -5,9 +5,6 @@ #include "FSPersistence.h" #include "JsonUtils.h" -#include -#include - #ifndef FACTORY_AP_PROVISION_MODE #define FACTORY_AP_PROVISION_MODE AP_MODE_DISCONNECTED #endif @@ -47,14 +44,10 @@ #define AP_SETTINGS_FILE "/config/apSettings.json" #define AP_SETTINGS_SERVICE_PATH "/rest/apSettings" -#define AP_MODE_ALWAYS 0 #define AP_MODE_DISCONNECTED 1 #define AP_MODE_NEVER 2 -#define MANAGE_NETWORK_DELAY 10000 -#define DNS_PORT 53 - -enum APNetworkStatus { ACTIVE = 0, INACTIVE, LINGERING }; +enum APNetworkStatus { ACTIVE = 0, INACTIVE }; class APSettings { public: @@ -84,26 +77,11 @@ class APSettingsService : public StatefulService { APSettingsService(AsyncWebServer * server, FS * fs, SecurityManager * securityManager); void begin(); - void loop(); APNetworkStatus getAPNetworkStatus(); private: HttpEndpoint _httpEndpoint; FSPersistence _fsPersistence; - - // for the captive portal - DNSServer * _dnsServer; - - // for the management delay loop - volatile unsigned long _lastManaged; - volatile bool _reconfigureAp; - volatile uint8_t _connected; - - void reconfigureAP(); - void manageAP(); - void startAP(); - void stopAP(); - void handleDNS(); }; #endif diff --git a/src/ESP32React/APStatus.cpp b/src/ESP32React/APStatus.cpp index c0baa482d..09d8c39b0 100644 --- a/src/ESP32React/APStatus.cpp +++ b/src/ESP32React/APStatus.cpp @@ -1,5 +1,7 @@ #include "APStatus.h" +#include + APStatus::APStatus(AsyncWebServer * server, SecurityManager * securityManager, APSettingsService * apSettingsService) : _apSettingsService(apSettingsService) { securityManager->addEndpoint(server, AP_STATUS_SERVICE_PATH, AuthenticationPredicates::IS_AUTHENTICATED, [this](AsyncWebServerRequest * request) { @@ -12,9 +14,9 @@ void APStatus::apStatus(AsyncWebServerRequest * request) { JsonObject root = response->getRoot(); root["status"] = _apSettingsService->getAPNetworkStatus(); - root["ip_address"] = WiFi.softAPIP().toString(); - root["mac_address"] = WiFi.softAPmacAddress(); - root["station_num"] = WiFi.softAPgetStationNum(); + root["ip_address"] = emsesp::EMSESP::network_.getLocalIP(); + root["mac_address"] = emsesp::EMSESP::network_.getMacAddress(); + root["station_num"] = emsesp::EMSESP::network_.getStationNum(); response->setLength(); request->send(response); diff --git a/src/ESP32React/APStatus.h b/src/ESP32React/APStatus.h index 23ee79305..1105a742a 100644 --- a/src/ESP32React/APStatus.h +++ b/src/ESP32React/APStatus.h @@ -1,12 +1,7 @@ #ifndef APStatus_h #define APStatus_h -#include -#include - #include -#include -#include #include "SecurityManager.h" #include "APSettingsService.h" diff --git a/src/ESP32React/ESP32React.cpp b/src/ESP32React/ESP32React.cpp index 4cba7ee79..79a3fb843 100644 --- a/src/ESP32React/ESP32React.cpp +++ b/src/ESP32React/ESP32React.cpp @@ -101,8 +101,6 @@ void ESP32React::begin() { } void ESP32React::loop() { - _networkSettingsService.loop(); - _apSettingsService.loop(); _mqttSettingsService.loop(); _ntpSettingsService.loop(); } diff --git a/src/ESP32React/ESP32React.h b/src/ESP32React/ESP32React.h index cbcea487e..17c849595 100644 --- a/src/ESP32React/ESP32React.h +++ b/src/ESP32React/ESP32React.h @@ -54,19 +54,6 @@ class ESP32React { return _mqttSettingsService.getMqttClient(); } - // - // special functions needed outside scope - // - - // true if AP is active - bool apStatus() { - return _apSettingsService.getAPNetworkStatus() == APNetworkStatus::ACTIVE; - } - - uint16_t getWifiReconnects() { - return _networkSettingsService.getWifiReconnects(); - } - private: AsyncWebServer * _server; SecuritySettingsService _securitySettingsService; diff --git a/src/ESP32React/MqttSettingsService.cpp b/src/ESP32React/MqttSettingsService.cpp index 7049c5713..3f53e8027 100644 --- a/src/ESP32React/MqttSettingsService.cpp +++ b/src/ESP32React/MqttSettingsService.cpp @@ -79,7 +79,7 @@ void MqttSettingsService::startClient() { } void MqttSettingsService::loop() { - if (_state.enabled && _mqttClient && _mqttClient->connected() && !emsesp::EMSESP::system_.network_connected()) { + if (_state.enabled && _mqttClient && _mqttClient->connected() && !emsesp::EMSESP::network_.network_connected()) { // emsesp::EMSESP::logger().info("Network connection dropped, stopping MQTT client"); _mqttClient->disconnect(true); } @@ -154,7 +154,7 @@ bool MqttSettingsService::configureMqtt() { } // only connect if WiFi is connected and MQTT is enabled - if (_state.enabled && emsesp::EMSESP::system_.network_connected() && !_state.host.isEmpty()) { + if (_state.enabled && emsesp::EMSESP::network_.network_connected() && !_state.host.isEmpty()) { // create the Last Will Testament topic (LWT) with the base prefixed. It has to be static because the client destroys the reference static char will_topic[FACTORY_MQTT_MAX_TOPIC_LENGTH]; if (_state.base.isEmpty()) { diff --git a/src/ESP32React/NTPSettingsService.cpp b/src/ESP32React/NTPSettingsService.cpp index 4faebb1b9..6040c21c8 100644 --- a/src/ESP32React/NTPSettingsService.cpp +++ b/src/ESP32React/NTPSettingsService.cpp @@ -19,8 +19,8 @@ void NTPSettingsService::begin() { } void NTPSettingsService::loop() { - if (_connected != emsesp::EMSESP::system_.network_connected()) { - _connected = emsesp::EMSESP::system_.network_connected(); + if (_connected != emsesp::EMSESP::network_.network_connected()) { + _connected = emsesp::EMSESP::network_.network_connected(); configureNTP(); } } diff --git a/src/ESP32React/NetworkSettingsService.cpp b/src/ESP32React/NetworkSettingsService.cpp index 29b12b19a..af80fe049 100644 --- a/src/ESP32React/NetworkSettingsService.cpp +++ b/src/ESP32React/NetworkSettingsService.cpp @@ -4,484 +4,13 @@ NetworkSettingsService::NetworkSettingsService(AsyncWebServer * server, FS * fs, SecurityManager * securityManager) : _httpEndpoint(NetworkSettings::read, NetworkSettings::update, this, server, NETWORK_SETTINGS_SERVICE_PATH, securityManager) - , _fsPersistence(NetworkSettings::read, NetworkSettings::update, this, fs, NETWORK_SETTINGS_FILE) - , _lastConnectionAttempt(0) - , _stopping(false) { - addUpdateHandler([this] { reconfigureWiFiConnection(); }, false); - // Eth is also bound to the WifiGeneric event handler - // Network.onEvent([this](arduino_event_id_t event, arduino_event_info_t info) { WiFiEvent(event, info); }); -} - -static bool formatBssid(const String & bssid, uint8_t (&mac)[6]) { - uint tmp[6]; - if (bssid.isEmpty() || sscanf(bssid.c_str(), "%X:%X:%X:%X:%X:%X", &tmp[0], &tmp[1], &tmp[2], &tmp[3], &tmp[4], &tmp[5]) != 6) { - return false; - } - for (uint8_t i = 0; i < 6; i++) { - mac[i] = static_cast(tmp[i]); - } - return true; + , _fsPersistence(NetworkSettings::read, NetworkSettings::update, this, fs, NETWORK_SETTINGS_FILE) { } void NetworkSettingsService::begin() { - // We want the device to come up in opmode=0 (WIFI_OFF), when erasing the flash this is not the default. - // If needed, we save opmode=0 before disabling persistence so the device boots with WiFi disabled in the future. - if (WiFi.getMode() != WIFI_OFF) { - WiFi.mode(WIFI_OFF); - } - - // Disable WiFi config persistance and auto reconnect - WiFi.persistent(false); - WiFi.setAutoReconnect(false); - - WiFi.mode(WIFI_STA); - WiFi.mode(WIFI_OFF); - - // scan settings give connect issues since arduino 2.0.14 and arduino 3.x.x with some wifi systems - // WiFi.setScanMethod(WIFI_ALL_CHANNEL_SCAN); // default is FAST_SCAN - // WiFi.setSortMethod(WIFI_CONNECT_AP_BY_SIGNAL); // is default, no need to set - _fsPersistence.readFromFS(); } -void NetworkSettingsService::reconfigureWiFiConnection() { - // do not disconnect for switching to eth, restart is needed - if (WiFi.isConnected() && _state.ssid.length() == 0) { - return; - } - - // disconnect and de-configure wifi - if (WiFi.disconnect(true)) { - _stopping = true; - } -} - -void NetworkSettingsService::loop() { - unsigned long currentMillis = millis(); - if (!_lastConnectionAttempt || static_cast(currentMillis - _lastConnectionAttempt) >= WIFI_RECONNECTION_DELAY) { - _lastConnectionAttempt = currentMillis; - manageSTA(); - } - static uint8_t connect = 0; - enum uint8_t { - CONNECT_IDLE = 0, - CONNECT_WAIT_ETH, - CONNECT_WAIT_IP4, - CONNECT_WAIT_ETH_IP4, - CONNECT_WAIT_IP6, - CONNECT_WAIT_ETH_IP6, - CONNECT_ETH_ACTIVE, - CONNECT_WIFI_ACTIVE - }; - switch (connect) { - default: - connect = CONNECT_IDLE; - break; - case CONNECT_IDLE: - if (ETH.started() && _state.ssid.length() == 0) { - emsesp::EMSESP::logger().info("ETH started"); - ETH.setHostname(emsesp::EMSESP::system_.hostname().c_str()); - ETH.enableIPv6(true); - if (_state.staticIPConfig) { - ETH.config(_state.localIP, _state.gatewayIP, _state.subnetMask, _state.dnsIP1, _state.dnsIP2); - } - connect = CONNECT_WAIT_ETH; - } - if (WiFi.isConnected()) { - emsesp::EMSESP::logger().info("Wifi connected"); - if (_state.tx_power == 0) { - setWiFiPowerOnRSSI(); - } - mDNS_start(); - emsesp::EMSESP::system_.has_ipv6(true); - connect = CONNECT_WAIT_IP4; - } - break; - case CONNECT_WAIT_ETH: - if (ETH.connected()) { - emsesp::EMSESP::logger().info("ETH connected"); - emsesp::EMSESP::system_.ethernet_connected(true); - mDNS_start(); - emsesp::EMSESP::system_.has_ipv6(true); - connect = CONNECT_WAIT_ETH_IP4; - } - break; - case CONNECT_WAIT_ETH_IP4: - if (ETH.hasIP()) { - emsesp::EMSESP::logger().info("ETH IPv4: %s", ETH.localIP().toString().c_str()); - connect = CONNECT_WAIT_ETH_IP6; - } - if (!ETH.connected()) { - connect = CONNECT_ETH_ACTIVE; - } - break; - case CONNECT_WAIT_ETH_IP6: - if (ETH.hasLinkLocalIPv6() && ETH.hasGlobalIPv6()) { - emsesp::EMSESP::system_.has_ipv6(true); - connect = CONNECT_ETH_ACTIVE; - } - if (!ETH.connected()) { - connect = CONNECT_ETH_ACTIVE; - } - break; - case CONNECT_ETH_ACTIVE: - if (!ETH.connected()) { - emsesp::EMSESP::logger().info("ETH disconnected"); - emsesp::EMSESP::system_.ethernet_connected(false); - emsesp::EMSESP::system_.has_ipv6(false); - connect = CONNECT_IDLE; - } - break; - case CONNECT_WAIT_IP4: - if (!WiFi.localIP().toString().isEmpty()) { - emsesp::EMSESP::logger().info("Wifi IPv4: %s", WiFi.localIP().toString().c_str()); - connect = CONNECT_WAIT_IP6; - } - if (!WiFi.isConnected()) { - connect = CONNECT_ETH_ACTIVE; - } - break; - case CONNECT_WAIT_IP6: - if (WiFi.linkLocalIPv6().toString() != "::" && WiFi.globalIPv6().toString() != "::") { - emsesp::EMSESP::logger().info("Wifi IPv6: %s, %s", WiFi.linkLocalIPv6().toString().c_str(), WiFi.globalIPv6().toString().c_str()); - emsesp::EMSESP::system_.has_ipv6(true); - connect = CONNECT_WIFI_ACTIVE; - } - if (!WiFi.isConnected()) { - connect = CONNECT_WIFI_ACTIVE; - } - break; - case CONNECT_WIFI_ACTIVE: - if (!WiFi.isConnected()) { - emsesp::EMSESP::logger().info("WiFi disconnected"); - if (_stopping) { - _lastConnectionAttempt = 0; - _stopping = false; - } - emsesp::EMSESP::system_.has_ipv6(false); - connect = CONNECT_IDLE; - } - break; - } -} - -void NetworkSettingsService::manageSTA() { - // Abort if already connected, or if we have no SSID - if (WiFi.isConnected() || _state.ssid.length() == 0) { - return; - } - - // Connect or reconnect as required - if ((WiFi.getMode() & WIFI_STA) == 0) { - WiFi.setHostname(_state.hostname.c_str()); // updates shared default_hostname buffer - WiFi.enableSTA(true); // creates the STA netif - WiFi.STA.setHostname(_state.hostname.c_str()); // pushes to esp_netif_set_hostname - WiFi.enableIPv6(true); - if (_state.staticIPConfig) { - WiFi.config(_state.localIP, _state.gatewayIP, _state.subnetMask, _state.dnsIP1, _state.dnsIP2); // configure for static IP - } - - // www.esp32.com/viewtopic.php?t=12055 - if (_state.bandwidth20) { - esp_wifi_set_bandwidth(static_cast(ESP_IF_WIFI_STA), WIFI_BW_HT20); - } else { - esp_wifi_set_bandwidth(static_cast(ESP_IF_WIFI_STA), WIFI_BW_HT40); - } - if (_state.nosleep) { - WiFi.setSleep(false); // turn off sleep - WIFI_PS_NONE - } - - // attempt to connect to the network - uint8_t bssid[6]; - if (formatBssid(_state.bssid, bssid)) { - WiFi.begin(_state.ssid.c_str(), _state.password.c_str(), 0, bssid); - } else { - WiFi.begin(_state.ssid.c_str(), _state.password.c_str()); - } - -#ifdef BOARD_C3_MINI_V1 - // always hardcode Tx power for Wemos CS Mini v1 - // v1 needs this value, see https://github.com/emsesp/EMS-ESP32/pull/620#discussion_r993173979 - // https://www.wemos.cc/en/latest/c3/c3_mini_1_0_0.html#about-wifi - WiFi.setTxPower(WIFI_POWER_8_5dBm); -#else - if (_state.tx_power != 0) { - // if not set to Auto (0) set the Tx power now - if (!WiFi.setTxPower(static_cast(_state.tx_power))) { - emsesp::EMSESP::logger().warning("Failed to set WiFi Tx Power"); - } - } -#endif - } else { // not connected but STA-mode active => disconnect - reconfigureWiFiConnection(); - } -} - -// set the TxPower based on the RSSI (signal strength), picking the lowest value -// code is based of RSSI (signal strength) and copied from Tasmota's WiFiSetTXpowerBasedOnRssi() which is copied ESPEasy's ESPEasyWifi.SetWiFiTXpower() function -void NetworkSettingsService::setWiFiPowerOnRSSI() { - // Range ESP32 : 2dBm - 20dBm - // 802.11b - wifi1 - // 802.11a - wifi2 - // 802.11g - wifi3 - // 802.11n - wifi4 - // 802.11ac - wifi5 - // 802.11ax - wifi6 - - int max_tx_pwr = MAX_TX_PWR_DBM_n; // assume wifi4 - int threshold = WIFI_SENSITIVITY_n + 120; // Margin in dBm * 10 on top of threshold - - // Assume AP sends with max set by ETSI standard. - // 2.4 GHz: 100 mWatt (20 dBm) - // US and some other countries allow 1000 mW (30 dBm) - int rssi = WiFi.RSSI() * 10; - int newrssi = rssi - 200; // We cannot send with over 20 dBm, thus it makes no sense to force higher TX power all the time. - - int min_tx_pwr = 0; - if (newrssi < threshold) { - min_tx_pwr = threshold - newrssi; - } - if (min_tx_pwr > max_tx_pwr) { - min_tx_pwr = max_tx_pwr; - } - - // from WiFIGeneric.h use: - // WIFI_POWER_19_5dBm = 78,// 19.5dBm - // WIFI_POWER_19dBm = 76,// 19dBm - // WIFI_POWER_18_5dBm = 74,// 18.5dBm - // WIFI_POWER_17dBm = 68,// 17dBm - // WIFI_POWER_15dBm = 60,// 15dBm - // WIFI_POWER_13dBm = 52,// 13dBm - // WIFI_POWER_11dBm = 44,// 11dBm - // WIFI_POWER_8_5dBm = 34,// 8.5dBm - // WIFI_POWER_7dBm = 28,// 7dBm - // WIFI_POWER_5dBm = 20,// 5dBm - // WIFI_POWER_2dBm = 8,// 2dBm - // WIFI_POWER_MINUS_1dBm = -4// -1dBm - wifi_power_t p = WIFI_POWER_2dBm; - if (min_tx_pwr > 185) - p = WIFI_POWER_19_5dBm; - else if (min_tx_pwr > 170) - p = WIFI_POWER_18_5dBm; - else if (min_tx_pwr > 150) - p = WIFI_POWER_17dBm; - else if (min_tx_pwr > 130) - p = WIFI_POWER_15dBm; - else if (min_tx_pwr > 110) - p = WIFI_POWER_13dBm; - else if (min_tx_pwr > 85) - p = WIFI_POWER_11dBm; - else if (min_tx_pwr > 70) - p = WIFI_POWER_8_5dBm; - else if (min_tx_pwr > 50) - p = WIFI_POWER_7dBm; - else if (min_tx_pwr > 20) - p = WIFI_POWER_5dBm; - -#if defined(EMSESP_DEBUG) - // emsesp::EMSESP::logger().debug("Recommended WiFi Tx Power (set_power %d, new power %d, rssi %d, threshold %d)", min_tx_pwr / 10, p, rssi, threshold); -#endif - - if (!WiFi.setTxPower(p)) { - emsesp::EMSESP::logger().warning("Failed to set WiFi Tx Power"); - } -} - -// start the multicast UDP service so EMS-ESP is discoverable via .local -void NetworkSettingsService::mDNS_start() const { -#ifndef EMSESP_STANDALONE - MDNS.end(); - - if (_state.enableMDNS) { - if (!MDNS.begin(emsesp::EMSESP::system_.hostname().c_str())) { - emsesp::EMSESP::logger().warning("Failed to start mDNS Responder service"); - return; - } - - std::string address_s = emsesp::EMSESP::system_.hostname() + ".local"; - - MDNS.addService("http", "tcp", 80); // add our web server and rest API - MDNS.addService("telnet", "tcp", 23); // add our telnet console - - // MDNS.addServiceTxt("http", "tcp", "version", EMSESP_APP_VERSION); - MDNS.addServiceTxt("http", "tcp", "address", address_s.c_str()); - - emsesp::EMSESP::logger().info("Starting mDNS Responder service"); - } -#endif -} - -const char * NetworkSettingsService::disconnectReason(uint8_t code) { -#ifndef EMSESP_STANDALONE - switch (code) { - case WIFI_REASON_UNSPECIFIED: // = 1, - return "unspecified"; - case WIFI_REASON_AUTH_EXPIRE: // = 2, - return "auth expire"; - case WIFI_REASON_AUTH_LEAVE: // = 3, - return "auth leave"; - case WIFI_REASON_ASSOC_EXPIRE: // = 4, - return "assoc expired"; - case WIFI_REASON_ASSOC_TOOMANY: // = 5, - return "assoc too many"; - case WIFI_REASON_NOT_AUTHED: // = 6, - return "not authenticated"; - case WIFI_REASON_NOT_ASSOCED: // = 7, - return "not assoc"; - case WIFI_REASON_ASSOC_LEAVE: // = 8, - return "assoc leave"; - case WIFI_REASON_ASSOC_NOT_AUTHED: // = 9, - return "assoc not authed"; - case WIFI_REASON_DISASSOC_PWRCAP_BAD: // = 10, - return "disassoc powerCAP bad"; - case WIFI_REASON_DISASSOC_SUPCHAN_BAD: // = 11, - return "disassoc supchan bad"; - case WIFI_REASON_IE_INVALID: // = 13, - return "IE invalid"; - case WIFI_REASON_MIC_FAILURE: // = 14, - return "MIC failure"; - case WIFI_REASON_4WAY_HANDSHAKE_TIMEOUT: // = 15, - return "4way handshake timeout"; - case WIFI_REASON_GROUP_KEY_UPDATE_TIMEOUT: // = 16, - return "group key-update timeout"; - case WIFI_REASON_IE_IN_4WAY_DIFFERS: // = 17, - return "IE in 4way differs"; - case WIFI_REASON_GROUP_CIPHER_INVALID: // = 18, - return "group cipher invalid"; - case WIFI_REASON_PAIRWISE_CIPHER_INVALID: // = 19, - return "pairwise cipher invalid"; - case WIFI_REASON_AKMP_INVALID: // = 20, - return "AKMP invalid"; - case WIFI_REASON_UNSUPP_RSN_IE_VERSION: // = 21, - return "unsupported RSN_IE version"; - case WIFI_REASON_INVALID_RSN_IE_CAP: // = 22, - return "invalid RSN_IE_CAP"; - case WIFI_REASON_802_1X_AUTH_FAILED: // = 23, - return "802 X1 auth failed"; - case WIFI_REASON_CIPHER_SUITE_REJECTED: // = 24, - return "cipher suite rejected"; - case WIFI_REASON_BEACON_TIMEOUT: // = 200, - return "beacon timeout"; - case WIFI_REASON_NO_AP_FOUND: // = 201, - return "no AP found"; - case WIFI_REASON_AUTH_FAIL: // = 202, - return "auth fail"; - case WIFI_REASON_ASSOC_FAIL: // = 203, - return "assoc fail"; - case WIFI_REASON_HANDSHAKE_TIMEOUT: // = 204, - return "handshake timeout"; - case WIFI_REASON_CONNECTION_FAIL: // 205, - return "connection fail"; - case WIFI_REASON_AP_TSF_RESET: // 206, - return "AP tsf reset"; - case WIFI_REASON_ROAMING: // 207, - return "roaming"; - case WIFI_REASON_ASSOC_COMEBACK_TIME_TOO_LONG: // 208, - return "assoc comeback time too long"; - case WIFI_REASON_SA_QUERY_TIMEOUT: // 209, - return "sa query timeout"; - default: - return "unknown"; - } -#endif - - return ""; -} - -// handles both WiFI and Ethernet -void NetworkSettingsService::WiFiEvent(arduino_event_id_t event, arduino_event_info_t info) { -#ifndef EMSESP_STANDALONE - - switch (event) { - case ARDUINO_EVENT_WIFI_STA_STOP: - if (_stopping) { - _lastConnectionAttempt = 0; - _stopping = false; - } - break; - - case ARDUINO_EVENT_WIFI_STA_DISCONNECTED: - connectcount_ = connectcount_ + 1; // count the number of WiFi reconnects - emsesp::EMSESP::logger().warning("WiFi disconnected (#%d). Reason: %s (%d)", - connectcount_, - disconnectReason(info.wifi_sta_disconnected.reason), - info.wifi_sta_disconnected.reason); // IDF 4.0 - emsesp::EMSESP::system_.has_ipv6(false); - - - break; - - case ARDUINO_EVENT_WIFI_STA_GOT_IP: - char result[10]; - emsesp::EMSESP::logger().info("WiFi connected (Local IP=%s, hostname=%s, TxPower=%s dBm)", - WiFi.localIP().toString().c_str(), - WiFi.getHostname(), - emsesp::Helpers::render_value(result, ((double)(WiFi.getTxPower()) / 4), 1)); - mDNS_start(); - break; - - case ARDUINO_EVENT_ETH_START: - // apply hostname FIRST so DHCP DISCOVER carries the correct name - ETH.setHostname(emsesp::EMSESP::system_.hostname().c_str()); - if (_state.staticIPConfig) { - ETH.config(_state.localIP, _state.gatewayIP, _state.subnetMask, _state.dnsIP1, _state.dnsIP2); - } - break; - - case ARDUINO_EVENT_ETH_GOT_IP: - // prevent double calls to mDNS - if (!emsesp::EMSESP::system_.ethernet_connected()) { - emsesp::EMSESP::logger().info("Ethernet connected (Local IP=%s, speed %d Mbps)", ETH.localIP().toString().c_str(), ETH.linkSpeed()); - emsesp::EMSESP::system_.ethernet_connected(true); - mDNS_start(); - } - break; - - case ARDUINO_EVENT_ETH_DISCONNECTED: - emsesp::EMSESP::logger().warning("Ethernet disconnected. Reason: %s (%d)", - disconnectReason(info.wifi_sta_disconnected.reason), - info.wifi_sta_disconnected.reason); - emsesp::EMSESP::system_.ethernet_connected(false); - emsesp::EMSESP::system_.has_ipv6(false); - break; - - case ARDUINO_EVENT_ETH_STOP: - emsesp::EMSESP::logger().info("Ethernet stopped"); - emsesp::EMSESP::system_.ethernet_connected(false); - emsesp::EMSESP::system_.has_ipv6(false); - break; - - case ARDUINO_EVENT_WIFI_STA_CONNECTED: - // Set the TxPower after the connection is established, if we're using TxPower = 0 (Auto) - if (_state.tx_power == 0) { - setWiFiPowerOnRSSI(); - } - break; - - case ARDUINO_EVENT_ETH_CONNECTED: - break; - - // IPv6 specific - WiFi/Eth - case ARDUINO_EVENT_WIFI_STA_GOT_IP6: - case ARDUINO_EVENT_ETH_GOT_IP6: { - auto ip6 = IPAddress(IPv6, (uint8_t *)info.got_ip6.ip6_info.ip.addr, 0).toString(); - const char * link = event == ARDUINO_EVENT_ETH_GOT_IP6 ? "Eth" : "WiFi"; - if (ip6.startsWith("fe80")) { - emsesp::EMSESP::logger().info("IPv6 (%s) local: %s", link, ip6.c_str()); - } else if (ip6.startsWith("fd") || ip6.startsWith("fc")) { - emsesp::EMSESP::logger().info("IPv6 (%s) ULA: %s", link, ip6.c_str()); - } else { - emsesp::EMSESP::logger().info("IPv6 (%s) global: %s", link, ip6.c_str()); - } - emsesp::EMSESP::system_.has_ipv6(true); - } break; - - default: - break; - } -#endif -} - void NetworkSettings::read(NetworkSettings & settings, JsonObject root) { // connection settings root["ssid"] = settings.ssid; @@ -544,6 +73,7 @@ StateUpdateResult NetworkSettings::update(JsonObject root, NetworkSettings & set } // see if we need to inform the user of a restart + // if tx power, enableCORS, CORSOrigin, ssid changes, we need to restart if (tx_power != settings.tx_power || enableCORS != settings.enableCORS || CORSOrigin != settings.CORSOrigin || (ssid != settings.ssid && settings.ssid.isEmpty())) { return StateUpdateResult::CHANGED_RESTART; // tell WebUI that a restart is needed diff --git a/src/ESP32React/NetworkSettingsService.h b/src/ESP32React/NetworkSettingsService.h index 4635c5181..c9903a681 100644 --- a/src/ESP32React/NetworkSettingsService.h +++ b/src/ESP32React/NetworkSettingsService.h @@ -8,15 +8,10 @@ #ifndef EMSESP_STANDALONE #include -#include -#include -#include -#include #endif #define NETWORK_SETTINGS_FILE "/config/networkSettings.json" #define NETWORK_SETTINGS_SERVICE_PATH "/rest/networkSettings" -#define WIFI_RECONNECTION_DELAY (1000 * 3) #ifndef FACTORY_WIFI_SSID #define FACTORY_WIFI_SSID "" @@ -30,37 +25,6 @@ #define FACTORY_WIFI_HOSTNAME "" #endif -// copied from Tasmota -#if CONFIG_IDF_TARGET_ESP32S2 -#define MAX_TX_PWR_DBM_11b 195 -#define MAX_TX_PWR_DBM_54g 150 -#define MAX_TX_PWR_DBM_n 130 -#define WIFI_SENSITIVITY_11b -880 -#define WIFI_SENSITIVITY_54g -750 -#define WIFI_SENSITIVITY_n -720 -#elif CONFIG_IDF_TARGET_ESP32S3 -#define MAX_TX_PWR_DBM_11b 210 -#define MAX_TX_PWR_DBM_54g 190 -#define MAX_TX_PWR_DBM_n 185 -#define WIFI_SENSITIVITY_11b -880 -#define WIFI_SENSITIVITY_54g -760 -#define WIFI_SENSITIVITY_n -720 -#elif CONFIG_IDF_TARGET_ESP32C2 || CONFIG_IDF_TARGET_ESP32C3 -#define MAX_TX_PWR_DBM_11b 210 -#define MAX_TX_PWR_DBM_54g 190 -#define MAX_TX_PWR_DBM_n 185 -#define WIFI_SENSITIVITY_11b -880 -#define WIFI_SENSITIVITY_54g -760 -#define WIFI_SENSITIVITY_n -730 -#else -#define MAX_TX_PWR_DBM_11b 195 -#define MAX_TX_PWR_DBM_54g 160 -#define MAX_TX_PWR_DBM_n 140 -#define WIFI_SENSITIVITY_11b -880 -#define WIFI_SENSITIVITY_54g -750 -#define WIFI_SENSITIVITY_n -700 -#endif - class NetworkSettings { public: // core wifi configuration @@ -92,27 +56,10 @@ class NetworkSettingsService : public StatefulService { NetworkSettingsService(AsyncWebServer * server, FS * fs, SecurityManager * securityManager); void begin(); - void loop(); - - uint16_t getWifiReconnects() const { - return connectcount_; - } private: HttpEndpoint _httpEndpoint; FSPersistence _fsPersistence; - - volatile unsigned long _lastConnectionAttempt; - volatile bool _stopping; - - volatile uint16_t connectcount_ = 0; // number of wifi reconnects - - void WiFiEvent(arduino_event_id_t event, arduino_event_info_t info); - void mDNS_start() const; - const char * disconnectReason(uint8_t code); - void reconfigureWiFiConnection(); - void manageSTA(); - void setWiFiPowerOnRSSI(); }; #endif diff --git a/src/ESP32React/NetworkStatus.cpp b/src/ESP32React/NetworkStatus.cpp index 9003be9b2..cbfbe5152 100644 --- a/src/ESP32React/NetworkStatus.cpp +++ b/src/ESP32React/NetworkStatus.cpp @@ -16,11 +16,10 @@ void NetworkStatus::networkStatus(AsyncWebServerRequest * request) { auto * response = new AsyncJsonResponse(false); JsonObject root = response->getRoot(); - bool ethernet_connected = emsesp::EMSESP::system_.ethernet_connected(); - wl_status_t wifi_status = WiFi.status(); + wl_status_t wifi_status = WiFi.status(); // see if Ethernet is connected - if (ethernet_connected) { + if (emsesp::EMSESP::network_.ethernet_connected()) { root["status"] = 10; // custom code #10 - ETHERNET_STATUS_CONNECTED root["hostname"] = ETH.getHostname(); } else { @@ -29,7 +28,7 @@ void NetworkStatus::networkStatus(AsyncWebServerRequest * request) { } // for both connections show ethernet - if (ethernet_connected) { + if (emsesp::EMSESP::network_.ethernet_connected()) { // Ethernet root["local_ip"] = ETH.localIP().toString(); root["local_ipv6"] = ETH.linkLocalIPv6().toString(); @@ -52,7 +51,7 @@ void NetworkStatus::networkStatus(AsyncWebServerRequest * request) { root["ssid"] = WiFi.SSID(); root["bssid"] = WiFi.BSSIDstr(); root["channel"] = WiFi.channel(); - root["reconnect_count"] = emsesp::EMSESP::esp32React.getWifiReconnects(); + root["reconnect_count"] = emsesp::EMSESP::network_.getWifiReconnects(); root["subnet_mask"] = WiFi.subnetMask().toString(); if (WiFi.gatewayIP() != INADDR_NONE) { diff --git a/src/core/console.cpp b/src/core/console.cpp index c84adaf2d..eb2d5146a 100644 --- a/src/core/console.cpp +++ b/src/core/console.cpp @@ -202,7 +202,7 @@ static void setup_commands(std::shared_ptr const & commands) { commands->add_command(ShellContext::MAIN, CommandFlags::ADMIN, string_vector{F_(wifi), F_(reconnect)}, - [](Shell & shell, const std::vector & arguments) { EMSESP::system_.wifi_reconnect(); }); + [](Shell & shell, const std::vector & arguments) { EMSESP::network_.reconnect(); }); // // SET commands @@ -250,12 +250,12 @@ static void setup_commands(std::shared_ptr const & commands) { shell.enter_password(F_(new_password_prompt2), [password1](Shell & shell, bool completed, const std::string & password2) { if (completed) { if (password1 == password2) { - EMSESP::esp32React.getNetworkSettingsService()->updateWithoutPropagation([&](NetworkSettings & networkSettings) { + EMSESP::esp32React.getNetworkSettingsService()->update([&](NetworkSettings & networkSettings) { networkSettings.password = password2.c_str(); return StateUpdateResult::CHANGED; }); shell.println("WiFi password updated. Reconnecting..."); - EMSESP::system_.wifi_reconnect(); + EMSESP::network_.reconnect(); } else { shell.println("Passwords do not match"); } @@ -277,6 +277,7 @@ static void setup_commands(std::shared_ptr const & commands) { networkSettings.hostname = arguments.front().c_str(); return StateUpdateResult::CHANGED; }); + EMSESP::network_.reconnect(); }); commands->add_command(ShellContext::MAIN, @@ -284,12 +285,14 @@ static void setup_commands(std::shared_ptr const & commands) { string_vector{F_(set), F_(wifi), F_(ssid)}, {F_(name_mandatory)}, [](Shell & shell, const std::vector & arguments) { - EMSESP::esp32React.getNetworkSettingsService()->updateWithoutPropagation([&](NetworkSettings & networkSettings) { + shell.println("The network connection will be reset..."); + Shell::loop_all(); + delay(1000); // wait a second + EMSESP::esp32React.getNetworkSettingsService()->update([&](NetworkSettings & networkSettings) { networkSettings.ssid = arguments.front().c_str(); return StateUpdateResult::CHANGED; }); - shell.println("WiFi ssid updated. Reconnecting..."); - EMSESP::system_.wifi_reconnect(); + EMSESP::network_.reconnect(); }); diff --git a/src/core/emsesp.cpp b/src/core/emsesp.cpp index a0fa75bc0..e811e7888 100644 --- a/src/core/emsesp.cpp +++ b/src/core/emsesp.cpp @@ -86,6 +86,7 @@ TxService EMSESP::txservice_; // outgoing Telegram Tx handler Mqtt EMSESP::mqtt_; // mqtt handler Modbus * EMSESP::modbus_ = nullptr; // modbus handler System EMSESP::system_; // core system services +Network EMSESP::network_; // network services TemperatureSensor EMSESP::temperaturesensor_; // Temperature sensors AnalogSensor EMSESP::analogsensor_; // Analog sensors Shower EMSESP::shower_; // Shower logic @@ -1721,13 +1722,13 @@ void EMSESP::start() { // start web log service. now we can start capturing logs to the web log webLogService.begin(); - // loads core system services settings (network, mqtt, ap, ntp etc) + // loads core system services settings (mqtt, ap, ntp etc) esp32React.begin(); #ifndef EMSESP_STANDALONE if (factory_settings) { LOG_WARNING("No settings found on filesystem. Using factory settings."); - // make sure OTAdata is updated with core3 format + // make sure OTAdata is updated with core3 (v3.9.0) format esp_ota_set_boot_partition(esp_ota_get_running_partition()); } #endif @@ -1777,6 +1778,9 @@ void EMSESP::start() { webSettingsService.begin(); // load EMS-ESP Application settings + // start network services. This will initialise WiFi or Ethernet depending on the settings. + network_.begin(); + // perform any system upgrades if (!factory_settings) { if (system_.check_upgrade()) { @@ -1847,6 +1851,9 @@ void EMSESP::shell_prompt() { void EMSESP::loop() { uuid::loop(); // store system uptime + // handle network + network_.loop(); + // handles LED and checks system health, and syslog service if (system_.loop()) { return; // LED flashing is active, skip the rest of the loop diff --git a/src/core/emsesp.h b/src/core/emsesp.h index 416c1bea7..cd9bd62c4 100644 --- a/src/core/emsesp.h +++ b/src/core/emsesp.h @@ -63,6 +63,7 @@ #include "mqtt.h" #include "modbus.h" #include "system.h" +#include "network.h" #include "temperaturesensor.h" #include "analogsensor.h" #include "console.h" @@ -228,6 +229,7 @@ class EMSESP { static Mqtt mqtt_; static Modbus * modbus_; static System system_; + static Network network_; static TemperatureSensor temperaturesensor_; static AnalogSensor analogsensor_; static Shower shower_; diff --git a/src/core/locale_common.h b/src/core/locale_common.h index d5b88c789..dc54f45fd 100644 --- a/src/core/locale_common.h +++ b/src/core/locale_common.h @@ -60,6 +60,7 @@ MAKE_WORD(mqtt) MAKE_WORD(gpio) MAKE_WORD(modbus) MAKE_WORD(emsesp) +MAKE_WORD(network) MAKE_WORD(connected) MAKE_WORD(disconnected) MAKE_WORD(passwd) diff --git a/src/core/mqtt.cpp b/src/core/mqtt.cpp index 7c63923f3..bb501cafb 100644 --- a/src/core/mqtt.cpp +++ b/src/core/mqtt.cpp @@ -563,7 +563,7 @@ void Mqtt::ha_status() { // create the HA sensors - must match the MQTT payload keys in the heartbeat topic // Note we don't use camelCase as it would change the HA entity_id and impact historic data #ifndef EMSESP_STANDALONE - if (!EMSESP::system_.ethernet_connected() || WiFi.isConnected()) { + if (EMSESP::network_.wifi_connected()) { publish_system_ha_sensor_config(DeviceValueType::INT8, "RSSI", "rssi", DeviceValueUOM::DBM); publish_system_ha_sensor_config(DeviceValueType::INT8, "Signal", "wifistrength", DeviceValueUOM::PERCENT); } @@ -585,7 +585,7 @@ void Mqtt::ha_status() { publish_system_ha_sensor_config(DeviceValueType::INT8, "CPU temperature", "temperature", DeviceValueUOM::DEGREES); #endif - if (!EMSESP::system_.ethernet_connected()) { + if (!EMSESP::network_.ethernet_connected()) { publish_system_ha_sensor_config(DeviceValueType::INT16, "WiFi reconnects", "wifireconnects", DeviceValueUOM::NONE); } // This one comes from the info MQTT topic - and handled in the publish_ha_sensor_config function diff --git a/src/core/network.cpp b/src/core/network.cpp new file mode 100644 index 000000000..6d0cb0a4e --- /dev/null +++ b/src/core/network.cpp @@ -0,0 +1,673 @@ +/* + * EMS-ESP - https://github.com/emsesp/EMS-ESP + * Copyright 2020-2025 emsesp.org + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "network.h" + +#include "emsesp.h" + +namespace emsesp { + +uuid::log::Logger Network::logger_{F_(network), uuid::log::Facility::KERN}; + +void Network::begin() { +#ifndef EMSESP_STANDALONE + // pull Network settings and store locally on stack + EMSESP::esp32React.getNetworkSettingsService()->read([&](auto & settings) { + enableMDNS_ = settings.enableMDNS; + staticIPConfig_ = settings.staticIPConfig; + localIP_ = settings.localIP; + gatewayIP_ = settings.gatewayIP; + subnetMask_ = settings.subnetMask; + dnsIP1_ = settings.dnsIP1; + dnsIP2_ = settings.dnsIP2; + hostname_ = settings.hostname; + ssid_ = settings.ssid; + password_ = settings.password; + bandwidth20_ = settings.bandwidth20; + nosleep_ = settings.nosleep; + tx_power_ = settings.tx_power; + bssid_ = settings.bssid; + }); + + // read Ethernet settings + EMSESP::webSettingsService.read([&](WebSettings & settings) { + phy_type_ = settings.phy_type; + eth_power_ = settings.eth_power; + eth_phy_addr_ = settings.eth_phy_addr; + eth_clock_mode_ = settings.eth_clock_mode; + }); + + // get Access Point settings + EMSESP::esp32React.getAPSettingsService()->read([&](APSettings & settings) { + ap_provisionMode_ = settings.provisionMode; + ap_ssid_ = settings.ssid; + ap_password_ = settings.password; + ap_channel_ = settings.channel; + ap_ssid_hidden_ = settings.ssidHidden; + ap_max_clients_ = settings.maxClients; + ap_localIP_ = settings.localIP; + ap_gatewayIP_ = settings.gatewayIP; + ap_subnetMask_ = settings.subnetMask; + }); + + // Initialise WiFi - we only do this once, when the network service is started + // We want the device to come up in opmode=0 (WIFI_OFF), when erasing the flash this is not the default. + // If needed, we save opmode=0 before disabling persistence so the device boots with WiFi disabled in the future. + if (WiFi.getMode() != WIFI_OFF) { + WiFi.mode(WIFI_OFF); + } + + // Disable WiFi config persistance and auto reconnect + WiFi.persistent(false); + WiFi.setAutoReconnect(false); + + WiFi.mode(WIFI_STA); + WiFi.mode(WIFI_OFF); + + // scan settings give connect issues since arduino 2.0.14 and arduino 3.x.x with some wifi systems + // WiFi.setScanMethod(WIFI_ALL_CHANNEL_SCAN); // default is FAST_SCAN + // WiFi.setSortMethod(WIFI_CONNECT_AP_BY_SIGNAL); // is default, no need to set + + // capture the WIFI_REASON_* code on every STA disconnect event so check_connection() can + // log a meaningful reason when its periodic poll notices we're no longer associated + WiFi.onEvent([this](WiFiEvent_t /*event*/, WiFiEventInfo_t info) { last_disconnect_reason_ = info.wifi_sta_disconnected.reason; }, + ARDUINO_EVENT_WIFI_STA_DISCONNECTED); + + // clear the saved reason on a fresh STA association so we don't log a stale code on the next disconnect + WiFi.onEvent([this](WiFiEvent_t /*event*/, WiFiEventInfo_t /*info*/) { last_disconnect_reason_ = 0; }, ARDUINO_EVENT_WIFI_STA_GOT_IP); +#endif +} + +// format the BSSID (MAC address) of the network interface +bool Network::formatBSSID(const String & bssid, uint8_t (&mac)[6]) { +#ifndef EMSESP_STANDALONE + uint tmp[6]; + if (bssid.isEmpty() || sscanf(bssid.c_str(), "%X:%X:%X:%X:%X:%X", &tmp[0], &tmp[1], &tmp[2], &tmp[3], &tmp[4], &tmp[5]) != 6) { + return false; + } + for (uint8_t i = 0; i < 6; i++) { + mac[i] = static_cast(tmp[i]); + } +#endif + return true; +} + +// get the local IP address of the network interface +std::string Network::getLocalIP() const { + switch (network_iface_) { +#ifndef EMSESP_STANDALONE + case NetIface::AP: + return WiFi.softAPIP().toString().c_str(); + case NetIface::WIFI: + return WiFi.localIP().toString().c_str(); + case NetIface::ETHERNET: + return ETH.localIP().toString().c_str(); + case NetIface::NONE: +#endif + default: + return ""; + } +} + +// get the MAC address of the network interface +std::string Network::getMacAddress() const { + switch (network_iface_) { +#ifndef EMSESP_STANDALONE + case NetIface::AP: + return WiFi.softAPmacAddress().c_str(); + case NetIface::WIFI: + return WiFi.macAddress().c_str(); + case NetIface::ETHERNET: + return ETH.macAddress().c_str(); + case NetIface::NONE: +#endif + default: + return ""; + } +} + +// get the number of stations connected to the AP +uint8_t Network::getStationNum() const { +#ifndef EMSESP_STANDALONE + return network_iface_ == NetIface::AP ? WiFi.softAPgetStationNum() : 0; +#else + return 0; +#endif +} + +// disconnect all WiFi, Eth and AP +// so we can starts searching again to reconnect +void Network::reconnect() { + LOG_DEBUG("Disconnecting all networks"); + +#ifndef EMSESP_STANDALONE + // disconnect WiFi + if (wifi_connected()) { + WiFi.disconnect(true); + } + + // disconnect AP + + if (WiFi.getMode() & WIFI_AP) { + stopAP(); + } +#endif + + // reset network state + network_ip_ = 0; + network_iface_ = NetIface::NONE; + has_ipv6_ = false; + juststopped_ = true; + + // reload the network settings + begin(); +} + +// network loop, looking for new and disconnecting networks +void Network::loop() { +#ifndef EMSESP_STANDALONE + // if we already have a Wifi or Ethernet connection then re-check every NETWORK_RECONNECTION_DELAY_LONG, otherwise NETWORK_RECONNECTION_DELAY_SHORT + const unsigned long currentMillis = millis(); + const uint32_t reconnectDelay = + (network_iface_ == NetIface::WIFI || network_iface_ == NetIface::ETHERNET) ? NETWORK_RECONNECTION_DELAY_LONG : NETWORK_RECONNECTION_DELAY_SHORT; + if (!lastConnectionAttempt_ || static_cast(currentMillis - lastConnectionAttempt_) >= reconnectDelay) { + lastConnectionAttempt_ = currentMillis; + + // manage network interfaces + startAP(); // Captive Portal (AP) + startWIFI(); // WiFi + startEthernet(); // Ethernet + + if (network_ip_ != 0) { + checkConnection(); // already have a connection: verify it's still alive + } + findNetworks(); // detect new connections + } + + // process DNS requests for the captive portal while the soft-AP is up + if (ap_dnsServer_) { + ap_dnsServer_->processNextRequest(); + } +#endif +} + +// Re-validate the currently active connection +// if a netif is no longer up or has lost its IP (cable unplugged, AP gone, DHCP lease lost, ...) we drop our state so +// find_networks() can pick up a new one +void Network::checkConnection() { +#ifndef EMSESP_STANDALONE + bool still_up = false; + for (esp_netif_t * netif = esp_netif_next_unsafe(NULL); netif != NULL; netif = esp_netif_next_unsafe(netif)) { + if (iface_from_desc(esp_netif_get_desc(netif)) != network_iface_) { + continue; + } + esp_netif_ip_info_t ip_info = {}; + if (esp_netif_is_netif_up(netif) && esp_netif_get_ip_info(netif, &ip_info) == ESP_OK && ip_info.ip.addr == network_ip_) { + still_up = true; + } + break; // only one active netif per kind in our world (sta / eth / ap) + } + + if (!still_up) { + if (network_iface_ == NetIface::WIFI) { + uint8_t reason = last_disconnect_reason_; + if (reason == 0) { + reason = WIFI_REASON_UNSPECIFIED; // event hasn't fired yet (or was cleared); avoid logging "0" + } + LOG_INFO("WiFi connection lost (reason %u: %s)", reason, disconnectReason(reason)); + } else { + LOG_INFO("Ethernet connection lost"); + } + reconnect(); + } +#endif +} + +// set the WiFi TxPower based on the RSSI (signal strength), picking the lowest value +// code is based of RSSI (signal strength) and copied from Tasmota's WiFiSetTXpowerBasedOnRssi() which is copied ESPEasy's ESPEasyWifi.SetWiFiTXpower() function +// Range ESP32 : 2dBm - 20dBm +// 802.11b - wifi1 +// 802.11a - wifi2 +// 802.11g - wifi3 +// 802.11n - wifi4 +// 802.11ac - wifi5 +// 802.11ax - wifi6 +// tx_power is the Tx power to set, 0 for auto +void Network::setWiFiPower(uint8_t tx_power) { +#ifndef EMSESP_STANDALONE + if (tx_power != 0) { + if (!WiFi.setTxPower(static_cast(tx_power))) { + emsesp::EMSESP::logger().warning("Failed to set WiFi Tx Power"); + } + return; + } + + int max_tx_pwr = MAX_TX_PWR_DBM_n; // assume wifi4 + int threshold = WIFI_SENSITIVITY_n + 120; // Margin in dBm * 10 on top of threshold + + // Assume AP sends with max set by ETSI standard. + // 2.4 GHz: 100 mWatt (20 dBm) + // US and some other countries allow 1000 mW (30 dBm) + int rssi = WiFi.RSSI() * 10; + int newrssi = rssi - 200; // We cannot send with over 20 dBm, thus it makes no sense to force higher TX power all the time. + + int min_tx_pwr = 0; + if (newrssi < threshold) { + min_tx_pwr = threshold - newrssi; + } + if (min_tx_pwr > max_tx_pwr) { + min_tx_pwr = max_tx_pwr; + } + + // from WiFIGeneric.h use: + wifi_power_t p = WIFI_POWER_2dBm; + if (min_tx_pwr > 185) + p = WIFI_POWER_19_5dBm; + else if (min_tx_pwr > 170) + p = WIFI_POWER_18_5dBm; + else if (min_tx_pwr > 150) + p = WIFI_POWER_17dBm; + else if (min_tx_pwr > 130) + p = WIFI_POWER_15dBm; + else if (min_tx_pwr > 110) + p = WIFI_POWER_13dBm; + else if (min_tx_pwr > 85) + p = WIFI_POWER_11dBm; + else if (min_tx_pwr > 70) + p = WIFI_POWER_8_5dBm; + else if (min_tx_pwr > 50) + p = WIFI_POWER_7dBm; + else if (min_tx_pwr > 20) + p = WIFI_POWER_5dBm; + +#if defined(EMSESP_DEBUG) + // emsesp::EMSESP::logger().debug("Recommended WiFi Tx Power (set_power %d, new power %d, rssi %d, threshold %d)", min_tx_pwr / 10, p, rssi, threshold); +#endif + + if (!WiFi.setTxPower(p)) { + emsesp::EMSESP::logger().warning("Failed to set WiFi Tx Power"); + } +#endif +} + +// start the multicast UDP service so EMS-ESP is discoverable via .local +void Network::startmDNS() const { +#ifndef EMSESP_STANDALONE + MDNS.end(); + + if (!enableMDNS_) { + return; + } + + if (!MDNS.begin(emsesp::EMSESP::system_.hostname().c_str())) { + emsesp::EMSESP::logger().warning("Failed to start mDNS Responder service"); + return; + } + + std::string address_s = emsesp::EMSESP::system_.hostname() + ".local"; + + MDNS.addService("http", "tcp", 80); // add our web server and rest API + MDNS.addService("telnet", "tcp", 23); // add our telnet console + MDNS.addServiceTxt("http", "tcp", "address", address_s.c_str()); + + emsesp::EMSESP::logger().info("Starting mDNS Responder service"); +#endif +} + +// WiFi disconnect reason code to string +const char * Network::disconnectReason(uint8_t code) { +#ifndef EMSESP_STANDALONE + switch (code) { + case WIFI_REASON_UNSPECIFIED: // = 1, + return "unspecified"; + case WIFI_REASON_AUTH_EXPIRE: // = 2, + return "auth expire"; + case WIFI_REASON_AUTH_LEAVE: // = 3, + return "auth leave"; + case WIFI_REASON_ASSOC_EXPIRE: // = 4, + return "assoc expired"; + case WIFI_REASON_ASSOC_TOOMANY: // = 5, + return "assoc too many"; + case WIFI_REASON_NOT_AUTHED: // = 6, + return "not authenticated"; + case WIFI_REASON_NOT_ASSOCED: // = 7, + return "not assoc"; + case WIFI_REASON_ASSOC_LEAVE: // = 8, + return "assoc leave"; + case WIFI_REASON_ASSOC_NOT_AUTHED: // = 9, + return "assoc not authed"; + case WIFI_REASON_DISASSOC_PWRCAP_BAD: // = 10, + return "disassoc powerCAP bad"; + case WIFI_REASON_DISASSOC_SUPCHAN_BAD: // = 11, + return "disassoc supchan bad"; + case WIFI_REASON_IE_INVALID: // = 13, + return "IE invalid"; + case WIFI_REASON_MIC_FAILURE: // = 14, + return "MIC failure"; + case WIFI_REASON_4WAY_HANDSHAKE_TIMEOUT: // = 15, + return "4way handshake timeout"; + case WIFI_REASON_GROUP_KEY_UPDATE_TIMEOUT: // = 16, + return "group key-update timeout"; + case WIFI_REASON_IE_IN_4WAY_DIFFERS: // = 17, + return "IE in 4way differs"; + case WIFI_REASON_GROUP_CIPHER_INVALID: // = 18, + return "group cipher invalid"; + case WIFI_REASON_PAIRWISE_CIPHER_INVALID: // = 19, + return "pairwise cipher invalid"; + case WIFI_REASON_AKMP_INVALID: // = 20, + return "AKMP invalid"; + case WIFI_REASON_UNSUPP_RSN_IE_VERSION: // = 21, + return "unsupported RSN_IE version"; + case WIFI_REASON_INVALID_RSN_IE_CAP: // = 22, + return "invalid RSN_IE_CAP"; + case WIFI_REASON_802_1X_AUTH_FAILED: // = 23, + return "802 X1 auth failed"; + case WIFI_REASON_CIPHER_SUITE_REJECTED: // = 24, + return "cipher suite rejected"; + case WIFI_REASON_BEACON_TIMEOUT: // = 200, + return "beacon timeout"; + case WIFI_REASON_NO_AP_FOUND: // = 201, + return "no AP found"; + case WIFI_REASON_AUTH_FAIL: // = 202, + return "auth fail"; + case WIFI_REASON_ASSOC_FAIL: // = 203, + return "assoc fail"; + case WIFI_REASON_HANDSHAKE_TIMEOUT: // = 204, + return "handshake timeout"; + case WIFI_REASON_CONNECTION_FAIL: // 205, + return "connection fail"; + case WIFI_REASON_AP_TSF_RESET: // 206, + return "AP tsf reset"; + case WIFI_REASON_ROAMING: // 207, + return "roaming"; + case WIFI_REASON_ASSOC_COMEBACK_TIME_TOO_LONG: // 208, + return "assoc comeback time too long"; + case WIFI_REASON_SA_QUERY_TIMEOUT: // 209, + return "sa query timeout"; + default: + return "unknown"; + } +#endif + + return ""; +} + +// WiFi management +void Network::startWIFI() { +#ifndef EMSESP_STANDALONE + // Abort if already connected, or if we have no SSID + if (WiFi.isConnected() || ssid_.length() == 0) { + return; + } + + LOG_DEBUG("Managing WiFi"); // TODO remove + + WiFi.setHostname(hostname_.c_str()); // updates shared default_hostname buffer + WiFi.enableSTA(true); // creates the STA netif + WiFi.STA.setHostname(hostname_.c_str()); // pushes to esp_netif_set_hostname + WiFi.enableIPv6(true); + if (staticIPConfig_) { + WiFi.config(localIP_, gatewayIP_, subnetMask_, dnsIP1_, dnsIP2_); // configure for static IP + } + + // www.esp32.com/viewtopic.php?t=12055 + if (bandwidth20_) { + esp_wifi_set_bandwidth(static_cast(ESP_IF_WIFI_STA), WIFI_BW_HT20); + } else { + esp_wifi_set_bandwidth(static_cast(ESP_IF_WIFI_STA), WIFI_BW_HT40); + } + if (nosleep_) { + WiFi.setSleep(false); // turn off sleep - WIFI_PS_NONE + } + + // attempt to connect to the network + uint8_t bssid[6]; + wl_status_t status; + if (formatBSSID(bssid_, bssid)) { + status = WiFi.begin(ssid_.c_str(), password_.c_str(), 0, bssid); + } else { + LOG_DEBUG("Connecting to WiFi SSID %s with password %s, hostname %s", ssid_.c_str(), password_.c_str(), hostname_.c_str()); // TODO remove + status = WiFi.begin(ssid_.c_str(), password_.c_str()); + } + if (status == WL_CONNECT_FAILED) { + LOG_ERROR("WiFi connection failed (code %d)", status); + } + +#ifdef BOARD_C3_MINI_V1 + // always hardcode Tx power for Wemos CS Mini v1 + // v1 needs this value, see https://github.com/emsesp/EMS-ESP32/pull/620#discussion_r993173979 + // https://www.wemos.cc/en/latest/c3/c3_mini_1_0_0.html#about-wifi + WiFi.setTxPower(WIFI_POWER_8_5dBm); +#else + setWiFiPower(tx_power_); +#endif + +#endif +} + +// Ethernet management +// Brings up the ETH driver / netif exactly once. After ETH.begin() returns true the driver +// continues to run autonomously (link negotiation, DHCP, etc); the loop must NOT call ETH.begin() +// again on every iteration because that thrashes the netif and can prevent DHCP from completing +void Network::startEthernet() { +#if CONFIG_IDF_TARGET_ESP32 + // already up and running, nothing to do + if (eth_started_) { + return; + } + +#ifndef EMSESP_STANDALONE + + // no ethernet present or wifi takes precedence + if (phy_type_ == PHY_type::PHY_TYPE_NONE || (ssid_.length() > 0)) { + return; + } + + LOG_DEBUG("Managing Ethernet"); // TODO remove + + // configure Ethernet + int mdc = 23; // Pin# of the I²C clock signal for the Ethernet PHY - hardcoded + int mdio = 18; // Pin# of the I²C IO signal for the Ethernet PHY - hardcoded + uint8_t phy_addr = eth_phy_addr_; // I²C-address of Ethernet PHY (0 or 1 for LAN8720, 31 for TLK110) + int8_t power = eth_power_; // Pin# of the enable signal for the external crystal oscillator (-1 to disable for internal APLL source) + eth_phy_type_t type = (phy_type_ == PHY_type::PHY_TYPE_LAN8720) ? ETH_PHY_LAN8720 + : (phy_type_ == PHY_type::PHY_TYPE_TLK110) ? ETH_PHY_TLK110 + : ETH_PHY_RTL8201; // Type of the Ethernet PHY (LAN8720 or TLK110) + // clock mode: + // ETH_CLOCK_GPIO0_IN = 0 RMII clock input to GPIO0 + // ETH_CLOCK_GPIO0_OUT = 1 RMII clock output from GPIO0 + // ETH_CLOCK_GPIO16_OUT = 2 RMII clock output from GPIO16 + // ETH_CLOCK_GPIO17_OUT = 3 RMII clock output from GPIO17, for 50hz inverted clock + auto clock_mode = (eth_clock_mode_t)eth_clock_mode_; + + // reset power and add a delay as ETH doesn't not always start up correctly after a warm boot + if (eth_power_ != -1) { + pinMode(eth_power_, OUTPUT); + digitalWrite(eth_power_, LOW); + delay(500); + digitalWrite(eth_power_, HIGH); + } + + if (ETH.begin(type, phy_addr, mdc, mdio, power, clock_mode)) { + eth_started_ = true; // mark up; do not re-enter this block until reboot / explicit teardown + ETH.setHostname(hostname_.c_str()); // Push hostname to the ETH netif immediately after it's created + ETH.enableIPv6(true); + if (staticIPConfig_) { + ETH.config(localIP_, gatewayIP_, subnetMask_, dnsIP1_, dnsIP2_); + } + } else { + LOG_ERROR("Failed to start Ethernet"); + } +#endif +#endif +} + +// check if the network is connected and set network_ip_ / network_iface_ / has_ipv6_ +// Iterates over every esp-netif that exists, prioritizing Ethernet > WiFi > AP +bool Network::findNetworks() { +#ifndef EMSESP_STANDALONE + + // exit if already have a connection, unless in AP mode + // when in AP mode, it will always try and connect to the WiFi + if (network_ip_ != 0 && !(WiFi.getMode() & WIFI_AP)) { + // const esp_ip4_addr_t ip4 = {.addr = network_ip_}; + // LOG_DEBUG("Network already connected via IPv4: " IPSTR, IP2STR(&ip4)); + return true; + } + + struct NetInfo { + esp_ip4_addr_t ip; + esp_ip6_addr_t ip6; + char desc[8]; + bool has_ipv6; + } info = {}; + + // Preference order: ETHERNET > WIFI (STA) > AP + auto iface_priority = [](NetIface iface) -> uint8_t { + switch (iface) { + case NetIface::ETHERNET: + return 3; + case NetIface::WIFI: + return 2; + case NetIface::AP: + return 1; + case NetIface::NONE: + default: + return 0; + } + }; + + NetIface best_iface = NetIface::NONE; + for (esp_netif_t * netif = esp_netif_next_unsafe(NULL); netif != NULL; netif = esp_netif_next_unsafe(netif)) { + const char * desc = esp_netif_get_desc(netif); + bool is_up = esp_netif_is_netif_up(netif); + esp_netif_ip_info_t ip_info = {}; + esp_err_t err = esp_netif_get_ip_info(netif, &ip_info); + + if (!is_up || err != ESP_OK || ip_info.ip.addr == 0) { + continue; + } + + const NetIface candidate = iface_from_desc(desc); + if (iface_priority(candidate) <= iface_priority(best_iface)) { + continue; // already have something at least as good + } + + info.ip = ip_info.ip; + if (desc) { + strlcpy(info.desc, desc, sizeof(info.desc)); + } + info.has_ipv6 = (esp_netif_get_ip6_linklocal(netif, &info.ip6) == ESP_OK); + best_iface = candidate; + + if (best_iface == NetIface::ETHERNET) { + break; // top priority, can't be beaten by anything later in the list + } + } + + auto previous_iface = NetIface::NONE; + // LOG_DEBUG("best_iface: %d, previous_iface: %d, network_iface_: %d", best_iface, previous_iface, network_iface_); // TODO remove + + // if we have a connection and it's a new one, set it up + if (best_iface != NetIface::NONE && best_iface != previous_iface) { + previous_iface = network_iface_; // save the previous interface for comparison next time + network_ip_ = info.ip.addr; + network_iface_ = iface_from_desc(info.desc); // "sta"/"ap"/"eth*" + has_ipv6_ = info.has_ipv6; + connnect_retry_ = 0; + + LOG_INFO("Network connected via %s (IP: " IPSTR ")", + network_iface_ == NetIface::ETHERNET ? "Ethernet" + : network_iface_ == NetIface::WIFI ? "WiFi" + : network_iface_ == NetIface::AP ? "AP" + : "unknown", + IP2STR(&info.ip)); + + // if we have a Eth or Wifi connection and the AP is running, stop it + if (network_iface_ != NetIface::AP && WiFi.getMode() & WIFI_AP) { + stopAP(); + } + + // count the number of restarts (for Wifi and Eth) + if (juststopped_) { + juststopped_ = false; + connectcount_++; + LOG_DEBUG("Network re-connection count %d", connectcount_); + } + + // start mDNS for any real network interface (skip the SoftAP since the captive portal handles its own DNS) + if (enableMDNS_ && network_iface_ != NetIface::AP && network_iface_ != NetIface::NONE) { + startmDNS(); + } + + return true; // we have a network connection + } + + // fallback + network_ip_ = 0; + network_iface_ = NetIface::NONE; + has_ipv6_ = false; + connnect_retry_++; + LOG_DEBUG("No active network interfaces found yet, re-connection count %d", connnect_retry_); +#endif + return false; // no connection found yet +} + +// access point (soft-AP) and the captive portal +void Network::startAP() { +#ifndef EMSESP_STANDALONE + // Only start AP as a fallback if the Network has failed + if (connnect_retry_ < MAX_NETWORK_RECONNECTION_ATTEMPTS) { + return; + } + + // don't start the soft-AP if it is disabled, or Ethernet has taken over or we have a real WiFi connection or it's already running + if (ap_provisionMode_ == AP_MODE_NEVER || network_connected() || WiFi.getMode() & WIFI_AP) { + return; + } + + WiFi.softAPenableIPv6(); // force IPv6, same as for STA - fixes https://github.com/emsesp/EMS-ESP32/issues/1922 + WiFi.softAPConfig(ap_localIP_, ap_gatewayIP_, ap_subnetMask_); + esp_wifi_set_bandwidth(static_cast(ESP_IF_WIFI_AP), WIFI_BW_HT20); + WiFi.softAP(ap_ssid_.c_str(), ap_password_.c_str(), ap_channel_, ap_ssid_hidden_, ap_max_clients_); +#if CONFIG_IDF_TARGET_ESP32C3 + WiFi.setTxPower(WIFI_POWER_8_5dBm); // https://www.wemos.cc/en/latest/c3/c3_mini_1_0_0.html#about-wifi +#endif + const IPAddress apIp = WiFi.softAPIP(); + LOG_INFO("Starting Access Point with captive portal on %u.%u.%u.%u", apIp[0], apIp[1], apIp[2], apIp[3]); + + // start DNS server for Captive Portal + ap_dnsServer_ = new DNSServer; + ap_dnsServer_->start(DNS_PORT, "*", apIp); +#endif +} + +// stop AP +void Network::stopAP() { + LOG_INFO("Stopping Access Point"); +#ifndef EMSESP_STANDALONE + WiFi.softAPdisconnect(true); + if (ap_dnsServer_) { + ap_dnsServer_->stop(); + delete ap_dnsServer_; + ap_dnsServer_ = nullptr; + } +#endif +} + +} // namespace emsesp diff --git a/src/core/network.h b/src/core/network.h new file mode 100644 index 000000000..4caf632b9 --- /dev/null +++ b/src/core/network.h @@ -0,0 +1,215 @@ +/* + * EMS-ESP - https://github.com/emsesp/EMS-ESP + * Copyright 2020-2025 emsesp.org + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef EMSESP_NETWORK_H_ +#define EMSESP_NETWORK_H_ + +#ifndef EMSESP_STANDALONE +#include +#include +#include +#include +#include +#include +#endif + +#include + +#include +#include + +namespace emsesp { + +#define NETWORK_RECONNECTION_DELAY_SHORT 2000 // 2 seconds +#define NETWORK_RECONNECTION_DELAY_LONG 60000 // 60 seconds + +#define MAX_NETWORK_RECONNECTION_ATTEMPTS 3 // maximum number of network reconnection attempts + +#define DNS_PORT 53 + +// copied from Tasmota +#if CONFIG_IDF_TARGET_ESP32S2 +#define MAX_TX_PWR_DBM_11b 195 +#define MAX_TX_PWR_DBM_54g 150 +#define MAX_TX_PWR_DBM_n 130 +#define WIFI_SENSITIVITY_11b -880 +#define WIFI_SENSITIVITY_54g -750 +#define WIFI_SENSITIVITY_n -720 +#elif CONFIG_IDF_TARGET_ESP32S3 +#define MAX_TX_PWR_DBM_11b 210 +#define MAX_TX_PWR_DBM_54g 190 +#define MAX_TX_PWR_DBM_n 185 +#define WIFI_SENSITIVITY_11b -880 +#define WIFI_SENSITIVITY_54g -760 +#define WIFI_SENSITIVITY_n -720 +#elif CONFIG_IDF_TARGET_ESP32C2 || CONFIG_IDF_TARGET_ESP32C3 +#define MAX_TX_PWR_DBM_11b 210 +#define MAX_TX_PWR_DBM_54g 190 +#define MAX_TX_PWR_DBM_n 185 +#define WIFI_SENSITIVITY_11b -880 +#define WIFI_SENSITIVITY_54g -760 +#define WIFI_SENSITIVITY_n -730 +#else +#define MAX_TX_PWR_DBM_11b 195 +#define MAX_TX_PWR_DBM_54g 160 +#define MAX_TX_PWR_DBM_n 140 +#define WIFI_SENSITIVITY_11b -880 +#define WIFI_SENSITIVITY_54g -750 +#define WIFI_SENSITIVITY_n -700 +#endif + +// which physical interface we are currently using for the active network connection. +// Mapped from the esp-netif description string returned by esp_netif_get_desc(): "sta" -> WIFI, +// "ap" -> AP, "eth"/"eth1"/"eth2"/... (arduino-esp32 v3.x suffixes ETH netifs because it supports +// multiple ETH instances) -> ETHERNET. Anything else stays as NONE. +enum class NetIface : uint8_t { + NONE = 0, + WIFI, + ETHERNET, + AP, +}; + +class Network { + public: + void begin(); + void loop(); + + uint16_t getWifiReconnects() const { + return connectcount_; + } + + uint32_t network_ip() const { + return network_ip_; + } + + NetIface network_iface() const { + return network_iface_; + } + + bool ethernet_connected() const { + return network_iface_ == NetIface::ETHERNET && network_ip_ != 0; + } + + bool wifi_connected() const { + return network_iface_ == NetIface::WIFI && network_ip_ != 0; + } + + bool ap_connected() const { + return network_iface_ == NetIface::AP && network_ip_ != 0; + } + + bool network_connected() const { + return ethernet_connected() || wifi_connected(); // don't include AP here + } + + bool has_ipv6() const { + return has_ipv6_ && (network_iface_ == NetIface::WIFI || network_iface_ == NetIface::ETHERNET); + } + + uint16_t getWifiReconnects() { + return connectcount_; + } + + std::string getLocalIP() const; + std::string getMacAddress() const; + uint8_t getStationNum() const; + + void reconnect(); + + // map a netif description string (from esp_netif_get_desc) to a NetIface + static NetIface iface_from_desc(const char * desc) { + if (!desc) { + return NetIface::NONE; + } + if (strcmp(desc, "sta") == 0) { + return NetIface::WIFI; + } + if (strcmp(desc, "ap") == 0) { + return NetIface::AP; + } + // any "eth*" (eth, eth0, eth1, ...) is treated as Ethernet + if (strncmp(desc, "eth", 3) == 0) { + return NetIface::ETHERNET; + } + return NetIface::NONE; + } + + private: + static uuid::log::Logger logger_; + + bool findNetworks(); + void checkConnection(); + void startmDNS() const; + bool formatBSSID(const String & bssid, uint8_t (&mac)[6]); + void startAP(); + void startWIFI(); + void startEthernet(); + void setWiFiPower(uint8_t tx_power); + const char * disconnectReason(uint8_t code); + void stopAP(); + + unsigned long lastConnectionAttempt_ = 0; + uint16_t connectcount_ = 0; // number of network reconnects + uint32_t network_ip_ = 0; + NetIface network_iface_ = NetIface::NONE; + bool has_ipv6_ = false; + bool juststopped_ = false; + bool eth_started_ = false; // true after ETH.begin() has succeeded once; prevents repeated re-init while DHCP is still running + volatile uint8_t last_disconnect_reason_ = 0; + uint16_t connnect_retry_ = 0; // number of network re-connection attempts + + // Network and AP settings + bool enableMDNS_; + bool staticIPConfig_; + IPAddress localIP_; + IPAddress gatewayIP_; + IPAddress subnetMask_; + IPAddress dnsIP1_; + IPAddress dnsIP2_; + String hostname_; + String ssid_; + String password_; + bool bandwidth20_; + bool nosleep_; + uint8_t tx_power_; + String bssid_; + uint8_t phy_type_; + int8_t eth_power_; + uint8_t eth_phy_addr_; + uint8_t eth_clock_mode_; + + // AP settings + uint8_t ap_provisionMode_; + String ap_ssid_; + String ap_password_; + uint8_t ap_channel_; + bool ap_ssid_hidden_; + uint8_t ap_max_clients_; + IPAddress ap_localIP_; + IPAddress ap_gatewayIP_; + IPAddress ap_subnetMask_; + +// for the captive portal in AP mode +#ifndef EMSESP_STANDALONE + DNSServer * ap_dnsServer_; +#endif +}; + +} // namespace emsesp + +#endif diff --git a/src/core/system.cpp b/src/core/system.cpp index b39ce5e15..08e7e38d1 100644 --- a/src/core/system.cpp +++ b/src/core/system.cpp @@ -17,6 +17,7 @@ */ #include "system.h" +#include "network.h" #include "emsesp.h" // for send_raw_telegram() command #ifndef EMSESP_STANDALONE @@ -485,7 +486,7 @@ void System::set_partition_install_date() { snprintf(c, sizeof(c), "d_%s", current_partition); time_t d = EMSESP::nvs_.getULong(c, 0); if (d < 1500000000L) { - LOG_DEBUG("Setting the install date in partition %s", current_partition); + LOG_DEBUG("Setting the NTP install date in partition %s", current_partition); auto t = time(nullptr) - uuid::get_uptime_sec(); EMSESP::nvs_.putULong(c, t); } @@ -586,15 +587,6 @@ void System::system_restart(const char * partitionname) { #endif } -// saves all settings -void System::wifi_reconnect() { - EMSESP::esp32React.getNetworkSettingsService()->read( - [](NetworkSettings & networkSettings) { LOG_INFO("WiFi reconnecting to SSID '%s'...", networkSettings.ssid.c_str()); }); - delay(500); // wait - EMSESP::webSettingsService.save(); // save local settings - EMSESP::esp32React.getNetworkSettingsService()->callUpdateHandlers(); // in case we've changed ssid or password -} - void System::syslog_init() { EMSESP::webSettingsService.read([&](WebSettings & settings) { syslog_enabled_ = settings.syslog_enabled; @@ -673,14 +665,9 @@ void System::store_settings(WebSettings & settings) { bool_dashboard_ = settings.bool_dashboard; enum_format_ = settings.enum_format; readonly_mode_ = settings.readonly_mode; - - phy_type_ = settings.phy_type; - eth_power_ = settings.eth_power; - eth_phy_addr_ = settings.eth_phy_addr; - eth_clock_mode_ = settings.eth_clock_mode; - locale_ = settings.locale; developer_mode_ = settings.developer_mode; + // start services if (settings.modbus_enabled) { if (EMSESP::modbus_ == nullptr) { @@ -736,9 +723,10 @@ void System::start() { commands_init(); // console & api commands led_init(); // init LED button_init(); // button - network_init(); // network - uart_init(); // start UART - syslog_init(); // start syslog + + last_system_check_ = 0; // force the LED to go from fast flash to pulse + uart_init(); // start UART + syslog_init(); // start syslog } // button single click @@ -756,9 +744,10 @@ void System::button_OnClick(PButton & b) { // button double click void System::button_OnDblClick(PButton & b) { LOG_NOTICE("Button pressed - double click - wifi reconnect to AP"); +#ifndef EMSESP_STANDALONE // set AP mode to always so will join AP if wifi ssid fails to connect EMSESP::esp32React.getAPSettingsService()->update([&](APSettings & apSettings) { - apSettings.provisionMode = AP_MODE_ALWAYS; + apSettings.provisionMode = AP_MODE_DISCONNECTED; return StateUpdateResult::CHANGED; }); // remove SSID from network settings @@ -766,7 +755,8 @@ void System::button_OnDblClick(PButton & b) { networkSettings.ssid = ""; return StateUpdateResult::CHANGED; }); - EMSESP::esp32React.getNetworkSettingsService()->callUpdateHandlers(); // in case we've changed ssid or password + EMSESP::network_.reconnect(); // reconnect to the network +#endif } // LED flash every 100ms @@ -904,7 +894,8 @@ bool System::loop() { // this is only done once when the connection is established void System::send_info_mqtt() { static uint8_t _connection = 0; - uint8_t connection = (ethernet_connected() ? 1 : 0) + ((WiFi.status() == WL_CONNECTED) ? 2 : 0) + (ntp_connected_ ? 4 : 0) + (has_ipv6_ ? 8 : 0); + uint8_t connection = (EMSESP::network_.ethernet_connected() ? 1 : 0) + (EMSESP::network_.wifi_connected() ? 2 : 0) + (ntp_connected_ ? 4 : 0) + + (EMSESP::network_.has_ipv6() ? 8 : 0); // check if connection status has changed if (!Mqtt::connected() || connection == _connection) { return; @@ -924,7 +915,7 @@ void System::send_info_mqtt() { } #ifndef EMSESP_STANDALONE - if (EMSESP::system_.ethernet_connected()) { + if (EMSESP::network_.ethernet_connected()) { doc["network"] = "ethernet"; doc["hostname"] = ETH.getHostname(); /* @@ -1003,11 +994,11 @@ void System::heartbeat_json(JsonObject output) { #endif #ifndef EMSESP_STANDALONE - if (!ethernet_connected_) { + if (!EMSESP::network_.ethernet_connected()) { int8_t rssi = WiFi.RSSI(); output["rssi"] = rssi; output["wifistrength"] = wifi_quality(rssi); - output["wifireconnects"] = EMSESP::esp32React.getWifiReconnects(); + output["wifireconnects"] = EMSESP::network_.getWifiReconnects(); } #endif } @@ -1023,49 +1014,6 @@ void System::send_heartbeat() { Mqtt::queue_publish(F_(heartbeat), json); // send to MQTT with retain off. This will add to MQTT queue. } -// initializes network -void System::network_init() { - last_system_check_ = 0; // force the LED to go from fast flash to pulse - -#if CONFIG_IDF_TARGET_ESP32 - bool disableEth; - EMSESP::esp32React.getNetworkSettingsService()->read([&](NetworkSettings & settings) { disableEth = settings.ssid.length() > 0; }); - - // no ethernet present or disabled - if (phy_type_ == PHY_type::PHY_TYPE_NONE || disableEth) { - return; - } // no ethernet present - - // configure Ethernet - int mdc = 23; // Pin# of the I²C clock signal for the Ethernet PHY - hardcoded - int mdio = 18; // Pin# of the I²C IO signal for the Ethernet PHY - hardcoded - uint8_t phy_addr = eth_phy_addr_; // I²C-address of Ethernet PHY (0 or 1 for LAN8720, 31 for TLK110) - int8_t power = eth_power_; // Pin# of the enable signal for the external crystal oscillator (-1 to disable for internal APLL source) - eth_phy_type_t type = (phy_type_ == PHY_type::PHY_TYPE_LAN8720) ? ETH_PHY_LAN8720 - : (phy_type_ == PHY_type::PHY_TYPE_TLK110) ? ETH_PHY_TLK110 - : ETH_PHY_RTL8201; // Type of the Ethernet PHY (LAN8720 or TLK110) - // clock mode: - // ETH_CLOCK_GPIO0_IN = 0 RMII clock input to GPIO0 - // ETH_CLOCK_GPIO0_OUT = 1 RMII clock output from GPIO0 - // ETH_CLOCK_GPIO16_OUT = 2 RMII clock output from GPIO16 - // ETH_CLOCK_GPIO17_OUT = 3 RMII clock output from GPIO17, for 50hz inverted clock - auto clock_mode = (eth_clock_mode_t)eth_clock_mode_; - - // reset power and add a delay as ETH doesn't not always start up correctly after a warm boot - if (eth_power_ != -1) { - pinMode(eth_power_, OUTPUT); - digitalWrite(eth_power_, LOW); - delay(500); - digitalWrite(eth_power_, HIGH); - } - eth_present_ = ETH.begin(type, phy_addr, mdc, mdio, power, clock_mode); - if (eth_present_) { - // Push hostname to the ETH netif immediately after it's created - ETH.setHostname(hostname_.c_str()); - } -#endif -} - // check health of system, done every 5 seconds void System::system_check() { uint32_t current_uptime = uuid::get_uptime(); @@ -1084,7 +1032,7 @@ void System::system_check() { #endif // check if we have a valid network connection - if (!ethernet_connected() && (WiFi.status() != WL_CONNECTED)) { + if (!EMSESP::network_.network_connected()) { healthcheck_ |= HEALTHCHECK_NO_NETWORK; } else { healthcheck_ &= ~HEALTHCHECK_NO_NETWORK; @@ -1402,7 +1350,7 @@ void System::show_system(uuid::console::Shell & shell) { } // show Ethernet if connected - if (ethernet_connected_) { + if (EMSESP::network_.ethernet_connected()) { shell.println(); shell.printfln(" Ethernet Status: connected"); shell.printfln(" Ethernet MAC address: %s", ETH.macAddress().c_str()); @@ -1694,6 +1642,7 @@ bool System::check_upgrade() { } return changed; }); + EMSESP::network_.reconnect(); } // changes to application settings @@ -2348,7 +2297,6 @@ std::string System::get_metrics_prometheus() { } result += info_metric; - // TODO fix, as local_info_labels is always empty here if (!local_info_labels.empty()) { result += "{"; bool first = true; @@ -2379,14 +2327,14 @@ String System::get_ip_or_hostname() { #ifndef EMSESP_STANDALONE EMSESP::esp32React.getNetworkSettingsService()->read([&](NetworkSettings & settings) { if (settings.enableMDNS) { - if (EMSESP::system_.ethernet_connected()) { + if (EMSESP::network_.ethernet_connected()) { result = ETH.getHostname(); } else if (WiFi.status() == WL_CONNECTED) { result = WiFi.getHostname(); } } else { // no DNS, use the IP - if (EMSESP::system_.ethernet_connected()) { + if (EMSESP::network_.ethernet_connected()) { result = ETH.localIP().toString(); } else if (WiFi.status() == WL_CONNECTED) { result = WiFi.localIP().toString(); @@ -2470,7 +2418,7 @@ bool System::command_info(const char * value, const int8_t id, JsonObject output // Network Status node = output["network"].to(); #ifndef EMSESP_STANDALONE - if (EMSESP::system_.ethernet_connected()) { + if (EMSESP::network_.ethernet_connected()) { node["network"] = "Ethernet"; node["hostname"] = ETH.getHostname(); // node["MAC"] = ETH.macAddress(); @@ -2484,7 +2432,7 @@ bool System::command_info(const char * value, const int8_t id, JsonObject output node["network"] = "WiFi"; node["hostname"] = WiFi.getHostname(); node["RSSI"] = WiFi.RSSI(); - node["WIFIReconnects"] = EMSESP::esp32React.getWifiReconnects(); + node["WIFIReconnects"] = EMSESP::network_.getWifiReconnects(); // node["MAC"] = WiFi.macAddress(); // node["IPv4 address"] = uuid::printable_to_string(WiFi.localIP()) + "/" + uuid::printable_to_string(WiFi.subnetMask()); // node["IPv4 gateway"] = uuid::printable_to_string(WiFi.gatewayIP()); diff --git a/src/core/system.h b/src/core/system.h index 52f6b0e5c..35a86b580 100644 --- a/src/core/system.h +++ b/src/core/system.h @@ -141,7 +141,6 @@ class System { static bool uploadFirmwareURL(const char * url = nullptr); void led_init(); - void network_init(); void button_init(); void commands_init(); void uart_init(); @@ -260,33 +259,9 @@ class System { hostname_ = hostname; } - bool ethernet_connected() { - return ethernet_connected_; - } - - void ethernet_connected(bool b) { - ethernet_connected_ = b; - } - - void has_ipv6(bool b) { - has_ipv6_ = b; - } - - bool has_ipv6() { - return has_ipv6_; - } - void ntp_connected(bool b); bool ntp_connected(); - bool network_connected() { -#ifndef EMSESP_STANDALONE - return (ethernet_connected() || WiFi.isConnected()); -#else - return true; -#endif - } - void fahrenheit(bool b) { fahrenheit_ = b; } @@ -312,8 +287,6 @@ class System { void show_system(uuid::console::Shell & shell); void show_users(uuid::console::Shell & shell); - void wifi_reconnect(); - static std::string languages_string(); uint32_t FStotal() { @@ -442,15 +415,11 @@ class System { uint8_t healthcheck_ = HEALTHCHECK_NO_NETWORK | HEALTHCHECK_NO_BUS; // start with all flags set, no wifi and no ems bus connection uint32_t last_system_check_ = 0; - bool upload_isrunning_ = false; // true if we're in the middle of a OTA firmware upload - bool ethernet_connected_ = false; - bool has_ipv6_ = false; + bool upload_isrunning_ = false; // true if we're in the middle of a OTA firmware upload bool ntp_connected_ = false; uint32_t ntp_last_check_ = 0; - bool eth_present_ = false; - // EMS-ESP settings std::string hostname_; String locale_; @@ -482,17 +451,10 @@ class System { uint8_t modbus_max_clients_; uint32_t modbus_timeout_; bool developer_mode_; - - // ethernet - uint8_t phy_type_; - int8_t eth_power_; - uint8_t eth_phy_addr_; - uint8_t eth_clock_mode_; - - uint32_t fstotal_; - uint32_t psram_; - uint32_t appused_; - uint32_t appfree_; + uint32_t fstotal_; + uint32_t psram_; + uint32_t appused_; + uint32_t appfree_; #if CONFIG_IDF_TARGET_ESP32S3 || CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32S2 temperature_sensor_handle_t temperature_handle_ = NULL; diff --git a/src/emsesp_version.h b/src/emsesp_version.h index 0aea80b67..1f2b49104 100644 --- a/src/emsesp_version.h +++ b/src/emsesp_version.h @@ -1 +1 @@ -#define EMSESP_APP_VERSION "3.9.0-dev.0" +#define EMSESP_APP_VERSION "3.9.0-dev.1" diff --git a/src/web/WebStatusService.cpp b/src/web/WebStatusService.cpp index d97c4d4e8..77a349839 100644 --- a/src/web/WebStatusService.cpp +++ b/src/web/WebStatusService.cpp @@ -79,9 +79,9 @@ void WebStatusService::systemStatus(AsyncWebServerRequest * request) { } #endif - root["ap_status"] = EMSESP::esp32React.apStatus(); + root["ap_status"] = EMSESP::network_.ap_connected(); - if (EMSESP::system_.ethernet_connected()) { + if (EMSESP::network_.ethernet_connected()) { root["network_status"] = 10; // custom code #10 - ETHERNET_STATUS_CONNECTED root["wifi_rssi"] = 0; } else { From 751f10603dab5ef9eda18e245e47a4d9f0fa6c92 Mon Sep 17 00:00:00 2001 From: proddy Date: Fri, 1 May 2026 17:03:56 +0200 Subject: [PATCH 35/48] upgrade if AP provision mode is AP_MODE_ALWAYS --- src/core/system.cpp | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/core/system.cpp b/src/core/system.cpp index 08e7e38d1..c89e3a6c3 100644 --- a/src/core/system.cpp +++ b/src/core/system.cpp @@ -1645,6 +1645,15 @@ bool System::check_upgrade() { EMSESP::network_.reconnect(); } + // changes going to v3.9 from an earlier version + if (settings_version.major() == 3 && settings_version.minor() < 9) { + EMSESP::esp32React.getAPSettingsService()->update([&](APSettings & apSettings) { + apSettings.provisionMode = AP_MODE_DISCONNECTED; // AP_MODE_ALWAYS has been removed + LOG_INFO("Upgrade: Setting AP provision mode to auto"); + return StateUpdateResult::CHANGED; + }); + } + // changes to application settings EMSESP::webSettingsService.update([&](WebSettings & settings) { // force web buffer to 25 for those boards without psram From e40beeadd48ea0df7f3f4eca4a14752ca7fdd69a Mon Sep 17 00:00:00 2001 From: proddy Date: Fri, 1 May 2026 17:04:46 +0200 Subject: [PATCH 36/48] performance updates --- src/core/network.cpp | 59 ++++++++++++++++++++++++-------------------- src/core/network.h | 3 ++- src/core/system.cpp | 9 ++++--- 3 files changed, 40 insertions(+), 31 deletions(-) diff --git a/src/core/network.cpp b/src/core/network.cpp index 6d0cb0a4e..d29978491 100644 --- a/src/core/network.cpp +++ b/src/core/network.cpp @@ -65,31 +65,38 @@ void Network::begin() { ap_subnetMask_ = settings.subnetMask; }); - // Initialise WiFi - we only do this once, when the network service is started - // We want the device to come up in opmode=0 (WIFI_OFF), when erasing the flash this is not the default. - // If needed, we save opmode=0 before disabling persistence so the device boots with WiFi disabled in the future. - if (WiFi.getMode() != WIFI_OFF) { - WiFi.mode(WIFI_OFF); - } + // Initialise WiFi - we only do this once, when the network service is started. + // We want the device to come up in opmode=0 (WIFI_OFF), which is not the default after a flash erase. + // Persistence is true by default, so this WiFi.mode() call writes opmode=0 to NVS for future boots. + WiFi.mode(WIFI_OFF); - // Disable WiFi config persistance and auto reconnect + // From here on, mode changes stay in RAM only and don't touch NVS WiFi.persistent(false); WiFi.setAutoReconnect(false); - WiFi.mode(WIFI_STA); - WiFi.mode(WIFI_OFF); // scan settings give connect issues since arduino 2.0.14 and arduino 3.x.x with some wifi systems // WiFi.setScanMethod(WIFI_ALL_CHANNEL_SCAN); // default is FAST_SCAN // WiFi.setSortMethod(WIFI_CONNECT_AP_BY_SIGNAL); // is default, no need to set // capture the WIFI_REASON_* code on every STA disconnect event so check_connection() can - // log a meaningful reason when its periodic poll notices we're no longer associated - WiFi.onEvent([this](WiFiEvent_t /*event*/, WiFiEventInfo_t info) { last_disconnect_reason_ = info.wifi_sta_disconnected.reason; }, - ARDUINO_EVENT_WIFI_STA_DISCONNECTED); + // log a meaningful reason when its periodic poll notices we're no longer associated. + // Also release the connect-pending guard so the next loop tick can issue a fresh WiFi.begin() + WiFi.onEvent( + [this](WiFiEvent_t /*event*/, WiFiEventInfo_t info) { + last_disconnect_reason_ = info.wifi_sta_disconnected.reason; + wifi_connect_pending_ = false; + LOG_WARNING("WiFi lost connection. Reason: %s", disconnectReason(last_disconnect_reason_)); + }, + ARDUINO_EVENT_WIFI_STA_DISCONNECTED); - // clear the saved reason on a fresh STA association so we don't log a stale code on the next disconnect - WiFi.onEvent([this](WiFiEvent_t /*event*/, WiFiEventInfo_t /*info*/) { last_disconnect_reason_ = 0; }, ARDUINO_EVENT_WIFI_STA_GOT_IP); + // clear the saved reason and the connect-pending guard on a fresh STA association + WiFi.onEvent( + [this](WiFiEvent_t /*event*/, WiFiEventInfo_t /*info*/) { + last_disconnect_reason_ = 0; + wifi_connect_pending_ = false; + }, + ARDUINO_EVENT_WIFI_STA_GOT_IP); #endif } @@ -169,12 +176,13 @@ void Network::reconnect() { #endif // reset network state - network_ip_ = 0; - network_iface_ = NetIface::NONE; - has_ipv6_ = false; - juststopped_ = true; + network_ip_ = 0; + network_iface_ = NetIface::NONE; + has_ipv6_ = false; + juststopped_ = true; + wifi_connect_pending_ = false; - // reload the network settings + // reload the network settings, as this could be called from the console begin(); } @@ -410,13 +418,11 @@ const char * Network::disconnectReason(uint8_t code) { // WiFi management void Network::startWIFI() { #ifndef EMSESP_STANDALONE - // Abort if already connected, or if we have no SSID - if (WiFi.isConnected() || ssid_.length() == 0) { + // Abort if already connected, or if we have no SSID or another Wifi.begin() is already in progress + if (WiFi.isConnected() || ssid_.length() == 0 || wifi_connect_pending_) { return; } - LOG_DEBUG("Managing WiFi"); // TODO remove - WiFi.setHostname(hostname_.c_str()); // updates shared default_hostname buffer WiFi.enableSTA(true); // creates the STA netif WiFi.STA.setHostname(hostname_.c_str()); // pushes to esp_netif_set_hostname @@ -438,13 +444,15 @@ void Network::startWIFI() { // attempt to connect to the network uint8_t bssid[6]; wl_status_t status; + wifi_connect_pending_ = true; // set before begin() so the event handlers can race-clear it safely + if (formatBSSID(bssid_, bssid)) { status = WiFi.begin(ssid_.c_str(), password_.c_str(), 0, bssid); } else { - LOG_DEBUG("Connecting to WiFi SSID %s with password %s, hostname %s", ssid_.c_str(), password_.c_str(), hostname_.c_str()); // TODO remove status = WiFi.begin(ssid_.c_str(), password_.c_str()); } if (status == WL_CONNECT_FAILED) { + wifi_connect_pending_ = false; // begin() didn't actually start anything, allow next tick to retry LOG_ERROR("WiFi connection failed (code %d)", status); } @@ -478,8 +486,6 @@ void Network::startEthernet() { return; } - LOG_DEBUG("Managing Ethernet"); // TODO remove - // configure Ethernet int mdc = 23; // Pin# of the I²C clock signal for the Ethernet PHY - hardcoded int mdio = 18; // Pin# of the I²C IO signal for the Ethernet PHY - hardcoded @@ -581,7 +587,6 @@ bool Network::findNetworks() { } auto previous_iface = NetIface::NONE; - // LOG_DEBUG("best_iface: %d, previous_iface: %d, network_iface_: %d", best_iface, previous_iface, network_iface_); // TODO remove // if we have a connection and it's a new one, set it up if (best_iface != NetIface::NONE && best_iface != previous_iface) { diff --git a/src/core/network.h b/src/core/network.h index 4caf632b9..9b5f7ed04 100644 --- a/src/core/network.h +++ b/src/core/network.h @@ -35,7 +35,7 @@ namespace emsesp { -#define NETWORK_RECONNECTION_DELAY_SHORT 2000 // 2 seconds +#define NETWORK_RECONNECTION_DELAY_SHORT 3000 // 3 seconds #define NETWORK_RECONNECTION_DELAY_LONG 60000 // 60 seconds #define MAX_NETWORK_RECONNECTION_ATTEMPTS 3 // maximum number of network reconnection attempts @@ -172,6 +172,7 @@ class Network { bool eth_started_ = false; // true after ETH.begin() has succeeded once; prevents repeated re-init while DHCP is still running volatile uint8_t last_disconnect_reason_ = 0; uint16_t connnect_retry_ = 0; // number of network re-connection attempts + volatile bool wifi_connect_pending_ = false; // Network and AP settings bool enableMDNS_; diff --git a/src/core/system.cpp b/src/core/system.cpp index c89e3a6c3..d291dfc70 100644 --- a/src/core/system.cpp +++ b/src/core/system.cpp @@ -1648,9 +1648,12 @@ bool System::check_upgrade() { // changes going to v3.9 from an earlier version if (settings_version.major() == 3 && settings_version.minor() < 9) { EMSESP::esp32React.getAPSettingsService()->update([&](APSettings & apSettings) { - apSettings.provisionMode = AP_MODE_DISCONNECTED; // AP_MODE_ALWAYS has been removed - LOG_INFO("Upgrade: Setting AP provision mode to auto"); - return StateUpdateResult::CHANGED; + if (apSettings.provisionMode == 0) { + apSettings.provisionMode = AP_MODE_DISCONNECTED; // AP_MODE_ALWAYS has been removed + LOG_INFO("Upgrade: Setting AP provision mode to auto"); + return StateUpdateResult::CHANGED; + } + return StateUpdateResult::UNCHANGED; }); } From df3d75c702180d2583ee2b7b560e3f9f5136b05c Mon Sep 17 00:00:00 2001 From: proddy Date: Fri, 1 May 2026 17:44:41 +0200 Subject: [PATCH 37/48] ignore .vscode/settings.json Co-authored-by: Cursor --- .gitignore | 2 + .vscode/settings.json | 101 ------------------------------------------ 2 files changed, 2 insertions(+), 101 deletions(-) delete mode 100644 .vscode/settings.json diff --git a/.gitignore b/.gitignore index bb18f41fa..1de26422a 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,7 @@ .vscode/c_cpp_properties.json .vscode/extensions.json .vscode/launch.json +.vscode/settings.json # c++ compiling .clang_complete @@ -75,3 +76,4 @@ pnpm-lock.yaml .cache/ interface/.tsbuildinfo test/test_api/package-lock.json +.clangd diff --git a/.vscode/settings.json b/.vscode/settings.json deleted file mode 100644 index 1893589ab..000000000 --- a/.vscode/settings.json +++ /dev/null @@ -1,101 +0,0 @@ -{ - "search.exclude": { - "**/.yarn": true, - "**/.pnp.*": true - }, - "editor.codeActionsOnSave": { - "source.fixAll": "explicit" - }, - "eslint.validate": [ - "typescript" - ], - "eslint.codeActionsOnSave.rules": null, - "eslint.nodePath": "interface/.yarn/sdks", - "eslint.workingDirectories": ["interface"], - "prettier.prettierPath": "", - "typescript.enablePromptUseWorkspaceTsdk": true, - "files.associations": { - "*.tsx": "typescriptreact", - "*.tcc": "cpp", - "optional": "cpp", - "istream": "cpp", - "ostream": "cpp", - "ratio": "cpp", - "system_error": "cpp", - "array": "cpp", - "functional": "cpp", - "regex": "cpp", - "tuple": "cpp", - "type_traits": "cpp", - "utility": "cpp", - "string": "cpp", - "string_view": "cpp", - "atomic": "cpp", - "bitset": "cpp", - "cctype": "cpp", - "chrono": "cpp", - "clocale": "cpp", - "cmath": "cpp", - "condition_variable": "cpp", - "cstdarg": "cpp", - "cstddef": "cpp", - "cstdint": "cpp", - "cstdio": "cpp", - "cstdlib": "cpp", - "cstring": "cpp", - "ctime": "cpp", - "cwchar": "cpp", - "cwctype": "cpp", - "deque": "cpp", - "list": "cpp", - "unordered_map": "cpp", - "unordered_set": "cpp", - "vector": "cpp", - "exception": "cpp", - "algorithm": "cpp", - "iterator": "cpp", - "map": "cpp", - "memory": "cpp", - "memory_resource": "cpp", - "numeric": "cpp", - "random": "cpp", - "set": "cpp", - "fstream": "cpp", - "initializer_list": "cpp", - "iomanip": "cpp", - "iosfwd": "cpp", - "iostream": "cpp", - "limits": "cpp", - "mutex": "cpp", - "new": "cpp", - "sstream": "cpp", - "stdexcept": "cpp", - "streambuf": "cpp", - "thread": "cpp", - "cinttypes": "cpp", - "typeinfo": "cpp" - }, - "todo-tree.filtering.excludeGlobs": [ - "**/vendor/**", - "**/node_modules/**", - "**/dist/**", - "**/bower_components/**", - "**/build/**", - "**/.vscode/**", - "**/.github/**", - "**/_output/**", - "**/*.min.*", - "**/*.map", - "**/ArduinoJson/**" - ], - "cSpell.enableFiletypes": [ - "ini", - "makefile" - ], - "typescript.preferences.preferTypeOnlyAutoImports": true, - "sonarlint.pathToCompileCommands": "${workspaceFolder}/compile_commands.json", - "sonarlint.connectedMode.project": { - "connectionId": "emsesp", - "projectKey": "emsesp_EMS-ESP32" - } - } \ No newline at end of file From 747047556e6e23e54e041980c83bff27cee3b1bb Mon Sep 17 00:00:00 2001 From: proddy Date: Fri, 1 May 2026 17:58:22 +0200 Subject: [PATCH 38/48] fix lint warnings on osx --- src/core/network.h | 9 +++++++++ src/core/system.cpp | 2 ++ 2 files changed, 11 insertions(+) diff --git a/src/core/network.h b/src/core/network.h index 9b5f7ed04..296187298 100644 --- a/src/core/network.h +++ b/src/core/network.h @@ -163,6 +163,11 @@ class Network { const char * disconnectReason(uint8_t code); void stopAP(); +#if defined(__clang__) +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wunused-private-field" +#endif + unsigned long lastConnectionAttempt_ = 0; uint16_t connectcount_ = 0; // number of network reconnects uint32_t network_ip_ = 0; @@ -205,6 +210,10 @@ class Network { IPAddress ap_gatewayIP_; IPAddress ap_subnetMask_; +#if defined(__clang__) +#pragma clang diagnostic pop +#endif + // for the captive portal in AP mode #ifndef EMSESP_STANDALONE DNSServer * ap_dnsServer_; diff --git a/src/core/system.cpp b/src/core/system.cpp index d291dfc70..f3cf50908 100644 --- a/src/core/system.cpp +++ b/src/core/system.cpp @@ -1647,6 +1647,7 @@ bool System::check_upgrade() { // changes going to v3.9 from an earlier version if (settings_version.major() == 3 && settings_version.minor() < 9) { + #ifndef EMSESP_STANDALONE EMSESP::esp32React.getAPSettingsService()->update([&](APSettings & apSettings) { if (apSettings.provisionMode == 0) { apSettings.provisionMode = AP_MODE_DISCONNECTED; // AP_MODE_ALWAYS has been removed @@ -1655,6 +1656,7 @@ bool System::check_upgrade() { } return StateUpdateResult::UNCHANGED; }); + #endif } // changes to application settings From 522286ff74abd6dc634a7c72002106477a2ce820 Mon Sep 17 00:00:00 2001 From: proddy Date: Sat, 2 May 2026 09:11:19 +0200 Subject: [PATCH 39/48] remove double wifi lost message --- src/core/network.cpp | 19 ++++++++++--------- src/core/system.cpp | 6 +++--- 2 files changed, 13 insertions(+), 12 deletions(-) diff --git a/src/core/network.cpp b/src/core/network.cpp index d29978491..74534642b 100644 --- a/src/core/network.cpp +++ b/src/core/network.cpp @@ -86,7 +86,6 @@ void Network::begin() { [this](WiFiEvent_t /*event*/, WiFiEventInfo_t info) { last_disconnect_reason_ = info.wifi_sta_disconnected.reason; wifi_connect_pending_ = false; - LOG_WARNING("WiFi lost connection. Reason: %s", disconnectReason(last_disconnect_reason_)); }, ARDUINO_EVENT_WIFI_STA_DISCONNECTED); @@ -169,18 +168,18 @@ void Network::reconnect() { } // disconnect AP - if (WiFi.getMode() & WIFI_AP) { stopAP(); } #endif // reset network state - network_ip_ = 0; - network_iface_ = NetIface::NONE; - has_ipv6_ = false; - juststopped_ = true; - wifi_connect_pending_ = false; + network_ip_ = 0; + network_iface_ = NetIface::NONE; + has_ipv6_ = false; + juststopped_ = true; + wifi_connect_pending_ = false; + last_disconnect_reason_ = 0; // reload the network settings, as this could be called from the console begin(); @@ -201,8 +200,10 @@ void Network::loop() { startWIFI(); // WiFi startEthernet(); // Ethernet - if (network_ip_ != 0) { - checkConnection(); // already have a connection: verify it's still alive + // already have a connection: verify it's still alive + // or trigger if the WiFi handshaked failed on the WiFi.begin() call + if (network_ip_ != 0 || last_disconnect_reason_ != 0) { + checkConnection(); } findNetworks(); // detect new connections } diff --git a/src/core/system.cpp b/src/core/system.cpp index f3cf50908..68f4e56e9 100644 --- a/src/core/system.cpp +++ b/src/core/system.cpp @@ -928,7 +928,7 @@ void System::send_info_mqtt() { } */ - } else if (WiFi.status() == WL_CONNECTED) { + } else if (EMSESP::network_.wifi_connected()) { doc["network"] = "wifi"; doc["hostname"] = WiFi.getHostname(); doc["SSID"] = WiFi.SSID(); @@ -1647,7 +1647,7 @@ bool System::check_upgrade() { // changes going to v3.9 from an earlier version if (settings_version.major() == 3 && settings_version.minor() < 9) { - #ifndef EMSESP_STANDALONE +#ifndef EMSESP_STANDALONE EMSESP::esp32React.getAPSettingsService()->update([&](APSettings & apSettings) { if (apSettings.provisionMode == 0) { apSettings.provisionMode = AP_MODE_DISCONNECTED; // AP_MODE_ALWAYS has been removed @@ -1656,7 +1656,7 @@ bool System::check_upgrade() { } return StateUpdateResult::UNCHANGED; }); - #endif +#endif } // changes to application settings From a57ed90756783057b801a6876786f5e9e652d02c Mon Sep 17 00:00:00 2001 From: proddy Date: Sat, 2 May 2026 09:48:19 +0200 Subject: [PATCH 40/48] use new network code --- src/web/WebStatusService.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/web/WebStatusService.cpp b/src/web/WebStatusService.cpp index fbcbeee4a..d12ddc226 100644 --- a/src/web/WebStatusService.cpp +++ b/src/web/WebStatusService.cpp @@ -374,10 +374,12 @@ void WebStatusService::getVersions(JsonObject root) { void WebStatusService::loop() { #ifndef EMSESP_STANDALONE // need a network - if (!EMSESP::system_.ethernet_connected() && (WiFi.status() != WL_CONNECTED)) { + if (!EMSESP::network_.network_connected()) { return; } + // TODO handle a network re-connect to fetch the values again (set versions_next_fetch_ms_ to 1) + // 0 = idle, nothing scheduled if (versions_next_fetch_ms_ == 0) { return; From 8f37bb762312fa127feb3436e410578e679f16c4 Mon Sep 17 00:00:00 2001 From: proddy Date: Sat, 2 May 2026 09:48:31 +0200 Subject: [PATCH 41/48] package update --- interface/package.json | 2 +- interface/pnpm-lock.yaml | 4770 ++++++++++---------------------------- mock-api/pnpm-lock.yaml | 14 +- 3 files changed, 1286 insertions(+), 3500 deletions(-) diff --git a/interface/package.json b/interface/package.json index 80f1bf7cd..38bef288e 100644 --- a/interface/package.json +++ b/interface/package.json @@ -51,7 +51,7 @@ "@types/react": "^19.2.14", "@types/react-dom": "^19.2.3", "concurrently": "^9.2.1", - "eslint": "^10.2.1", + "eslint": "^10.3.0", "eslint-config-prettier": "^10.1.8", "prettier": "^3.8.3", "rollup-plugin-visualizer": "^7.0.1", diff --git a/interface/pnpm-lock.yaml b/interface/pnpm-lock.yaml index 5c26d35cd..b19239666 100644 --- a/interface/pnpm-lock.yaml +++ b/interface/pnpm-lock.yaml @@ -5,6 +5,7 @@ settings: excludeLinksFromLockfile: false importers: + .: dependencies: '@alova/adapter-xhr': @@ -67,7 +68,7 @@ importers: devDependencies: '@eslint/js': specifier: ^10.0.1 - version: 10.0.1(eslint@10.2.1) + version: 10.0.1(eslint@10.3.0) '@preact/preset-vite': specifier: ^2.10.5 version: 2.10.5(@babel/core@7.29.0)(preact@10.29.1)(rollup@4.59.0)(vite@8.0.10(@types/node@25.6.0)(esbuild@0.27.4)(terser@5.46.2)) @@ -87,11 +88,11 @@ importers: specifier: ^9.2.1 version: 9.2.1 eslint: - specifier: ^10.2.1 - version: 10.2.1 + specifier: ^10.3.0 + version: 10.3.0 eslint-config-prettier: specifier: ^10.1.8 - version: 10.1.8(eslint@10.2.1) + version: 10.1.8(eslint@10.3.0) prettier: specifier: ^3.8.3 version: 3.8.3 @@ -103,7 +104,7 @@ importers: version: 5.46.2 typescript-eslint: specifier: ^8.59.1 - version: 8.59.1(eslint@10.2.1)(typescript@6.0.3) + version: 8.59.1(eslint@10.3.0)(typescript@6.0.3) vite: specifier: ^8.0.10 version: 8.0.10(@types/node@25.6.0)(esbuild@0.27.4)(terser@5.46.2) @@ -112,236 +113,138 @@ importers: version: 0.6.1(vite@8.0.10(@types/node@25.6.0)(esbuild@0.27.4)(terser@5.46.2)) packages: + '@alova/adapter-xhr@2.3.1': - resolution: - { - integrity: sha512-mhViueeSJ6yfyEUvHIWyZlb6V8+9GJCzwU1hUsvjXSZHpkzGCeNUefxjjFC++K9UBsvUzJ5KGeyeqoToUQg3gg== - } + resolution: {integrity: sha512-mhViueeSJ6yfyEUvHIWyZlb6V8+9GJCzwU1hUsvjXSZHpkzGCeNUefxjjFC++K9UBsvUzJ5KGeyeqoToUQg3gg==} peerDependencies: alova: ^3.0.20 '@alova/shared@1.3.2': - resolution: - { - integrity: sha512-1XvDLWgYpVZ99MmLl1f3Fw4T6S6pPYk5afz5cwRVjuq8JXEGsDn9IygDKfvRyWqkqCBx7Jif07LIct1O+MVEow== - } + resolution: {integrity: sha512-1XvDLWgYpVZ99MmLl1f3Fw4T6S6pPYk5afz5cwRVjuq8JXEGsDn9IygDKfvRyWqkqCBx7Jif07LIct1O+MVEow==} '@babel/code-frame@7.29.0': - resolution: - { - integrity: sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw== - } - engines: { node: '>=6.9.0' } + resolution: {integrity: sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==} + engines: {node: '>=6.9.0'} - '@babel/compat-data@7.29.0': - resolution: - { - integrity: sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg== - } - engines: { node: '>=6.9.0' } + '@babel/compat-data@7.29.3': + resolution: {integrity: sha512-LIVqM46zQWZhj17qA8wb4nW/ixr2y1Nw+r1etiAWgRM6U1IqP+LNhL1yg440jYZR72jCWcWbLWzIosH+uP1fqg==} + engines: {node: '>=6.9.0'} '@babel/core@7.29.0': - resolution: - { - integrity: sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA== - } - engines: { node: '>=6.9.0' } + resolution: {integrity: sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==} + engines: {node: '>=6.9.0'} '@babel/generator@7.29.1': - resolution: - { - integrity: sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw== - } - engines: { node: '>=6.9.0' } + resolution: {integrity: sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==} + engines: {node: '>=6.9.0'} '@babel/helper-annotate-as-pure@7.27.3': - resolution: - { - integrity: sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg== - } - engines: { node: '>=6.9.0' } + resolution: {integrity: sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg==} + engines: {node: '>=6.9.0'} '@babel/helper-compilation-targets@7.28.6': - resolution: - { - integrity: sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA== - } - engines: { node: '>=6.9.0' } + resolution: {integrity: sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==} + engines: {node: '>=6.9.0'} '@babel/helper-globals@7.28.0': - resolution: - { - integrity: sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw== - } - engines: { node: '>=6.9.0' } + resolution: {integrity: sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==} + engines: {node: '>=6.9.0'} '@babel/helper-module-imports@7.28.6': - resolution: - { - integrity: sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw== - } - engines: { node: '>=6.9.0' } + resolution: {integrity: sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==} + engines: {node: '>=6.9.0'} '@babel/helper-module-transforms@7.28.6': - resolution: - { - integrity: sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA== - } - engines: { node: '>=6.9.0' } + resolution: {integrity: sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==} + engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0 '@babel/helper-plugin-utils@7.28.6': - resolution: - { - integrity: sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug== - } - engines: { node: '>=6.9.0' } + resolution: {integrity: sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==} + engines: {node: '>=6.9.0'} '@babel/helper-string-parser@7.27.1': - resolution: - { - integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA== - } - engines: { node: '>=6.9.0' } + resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==} + engines: {node: '>=6.9.0'} '@babel/helper-validator-identifier@7.28.5': - resolution: - { - integrity: sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q== - } - engines: { node: '>=6.9.0' } + resolution: {integrity: sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==} + engines: {node: '>=6.9.0'} '@babel/helper-validator-option@7.27.1': - resolution: - { - integrity: sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg== - } - engines: { node: '>=6.9.0' } + resolution: {integrity: sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==} + engines: {node: '>=6.9.0'} '@babel/helpers@7.29.2': - resolution: - { - integrity: sha512-HoGuUs4sCZNezVEKdVcwqmZN8GoHirLUcLaYVNBK2J0DadGtdcqgr3BCbvH8+XUo4NGjNl3VOtSjEKNzqfFgKw== - } - engines: { node: '>=6.9.0' } + resolution: {integrity: sha512-HoGuUs4sCZNezVEKdVcwqmZN8GoHirLUcLaYVNBK2J0DadGtdcqgr3BCbvH8+XUo4NGjNl3VOtSjEKNzqfFgKw==} + engines: {node: '>=6.9.0'} - '@babel/parser@7.29.2': - resolution: - { - integrity: sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA== - } - engines: { node: '>=6.0.0' } + '@babel/parser@7.29.3': + resolution: {integrity: sha512-b3ctpQwp+PROvU/cttc4OYl4MzfJUWy6FZg+PMXfzmt/+39iHVF0sDfqay8TQM3JA2EUOyKcFZt75jWriQijsA==} + engines: {node: '>=6.0.0'} hasBin: true '@babel/plugin-syntax-jsx@7.28.6': - resolution: - { - integrity: sha512-wgEmr06G6sIpqr8YDwA2dSRTE3bJ+V0IfpzfSY3Lfgd7YWOaAdlykvJi13ZKBt8cZHfgH1IXN+CL656W3uUa4w== - } - engines: { node: '>=6.9.0' } + resolution: {integrity: sha512-wgEmr06G6sIpqr8YDwA2dSRTE3bJ+V0IfpzfSY3Lfgd7YWOaAdlykvJi13ZKBt8cZHfgH1IXN+CL656W3uUa4w==} + engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 '@babel/plugin-transform-react-jsx-development@7.27.1': - resolution: - { - integrity: sha512-ykDdF5yI4f1WrAolLqeF3hmYU12j9ntLQl/AOG1HAS21jxyg1Q0/J/tpREuYLfatGdGmXp/3yS0ZA76kOlVq9Q== - } - engines: { node: '>=6.9.0' } + resolution: {integrity: sha512-ykDdF5yI4f1WrAolLqeF3hmYU12j9ntLQl/AOG1HAS21jxyg1Q0/J/tpREuYLfatGdGmXp/3yS0ZA76kOlVq9Q==} + engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 '@babel/plugin-transform-react-jsx@7.28.6': - resolution: - { - integrity: sha512-61bxqhiRfAACulXSLd/GxqmAedUSrRZIu/cbaT18T1CetkTmtDN15it7i80ru4DVqRK1WMxQhXs+Lf9kajm5Ow== - } - engines: { node: '>=6.9.0' } + resolution: {integrity: sha512-61bxqhiRfAACulXSLd/GxqmAedUSrRZIu/cbaT18T1CetkTmtDN15it7i80ru4DVqRK1WMxQhXs+Lf9kajm5Ow==} + engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 '@babel/runtime@7.29.2': - resolution: - { - integrity: sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g== - } - engines: { node: '>=6.9.0' } + resolution: {integrity: sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g==} + engines: {node: '>=6.9.0'} '@babel/template@7.28.6': - resolution: - { - integrity: sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ== - } - engines: { node: '>=6.9.0' } + resolution: {integrity: sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==} + engines: {node: '>=6.9.0'} '@babel/traverse@7.29.0': - resolution: - { - integrity: sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA== - } - engines: { node: '>=6.9.0' } + resolution: {integrity: sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==} + engines: {node: '>=6.9.0'} '@babel/types@7.29.0': - resolution: - { - integrity: sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A== - } - engines: { node: '>=6.9.0' } + resolution: {integrity: sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==} + engines: {node: '>=6.9.0'} '@emnapi/core@1.10.0': - resolution: - { - integrity: sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw== - } + resolution: {integrity: sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==} '@emnapi/runtime@1.10.0': - resolution: - { - integrity: sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA== - } + resolution: {integrity: sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==} '@emnapi/wasi-threads@1.2.1': - resolution: - { - integrity: sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w== - } + resolution: {integrity: sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==} '@emotion/babel-plugin@11.13.5': - resolution: - { - integrity: sha512-pxHCpT2ex+0q+HH91/zsdHkw/lXd468DIN2zvfvLtPKLLMo6gQj7oLObq8PhkrxOZb/gGCq03S3Z7PDhS8pduQ== - } + resolution: {integrity: sha512-pxHCpT2ex+0q+HH91/zsdHkw/lXd468DIN2zvfvLtPKLLMo6gQj7oLObq8PhkrxOZb/gGCq03S3Z7PDhS8pduQ==} '@emotion/cache@11.14.0': - resolution: - { - integrity: sha512-L/B1lc/TViYk4DcpGxtAVbx0ZyiKM5ktoIyafGkH6zg/tj+mA+NE//aPYKG0k8kCHSHVJrpLpcAlOBEXQ3SavA== - } + resolution: {integrity: sha512-L/B1lc/TViYk4DcpGxtAVbx0ZyiKM5ktoIyafGkH6zg/tj+mA+NE//aPYKG0k8kCHSHVJrpLpcAlOBEXQ3SavA==} '@emotion/hash@0.9.2': - resolution: - { - integrity: sha512-MyqliTZGuOm3+5ZRSaaBGP3USLw6+EGykkwZns2EPC5g8jJ4z9OrdZY9apkl3+UP9+sdz76YYkwCKP5gh8iY3g== - } + resolution: {integrity: sha512-MyqliTZGuOm3+5ZRSaaBGP3USLw6+EGykkwZns2EPC5g8jJ4z9OrdZY9apkl3+UP9+sdz76YYkwCKP5gh8iY3g==} '@emotion/is-prop-valid@1.4.0': - resolution: - { - integrity: sha512-QgD4fyscGcbbKwJmqNvUMSE02OsHUa+lAWKdEUIJKgqe5IwRSKd7+KhibEWdaKwgjLj0DRSHA9biAIqGBk05lw== - } + resolution: {integrity: sha512-QgD4fyscGcbbKwJmqNvUMSE02OsHUa+lAWKdEUIJKgqe5IwRSKd7+KhibEWdaKwgjLj0DRSHA9biAIqGBk05lw==} '@emotion/memoize@0.9.0': - resolution: - { - integrity: sha512-30FAj7/EoJ5mwVPOWhAyCX+FPfMDrVecJAM+Iw9NRoSl4BBAQeqj4cApHHUXOVvIPgLVDsCFoz/hGD+5QQD1GQ== - } + resolution: {integrity: sha512-30FAj7/EoJ5mwVPOWhAyCX+FPfMDrVecJAM+Iw9NRoSl4BBAQeqj4cApHHUXOVvIPgLVDsCFoz/hGD+5QQD1GQ==} '@emotion/react@11.14.0': - resolution: - { - integrity: sha512-O000MLDBDdk/EohJPFUqvnp4qnHeYkVP5B0xEG0D/L7cOKP9kefu2DXn8dj74cQfsEzUqh+sr1RzFqiL1o+PpA== - } + resolution: {integrity: sha512-O000MLDBDdk/EohJPFUqvnp4qnHeYkVP5B0xEG0D/L7cOKP9kefu2DXn8dj74cQfsEzUqh+sr1RzFqiL1o+PpA==} peerDependencies: '@types/react': '*' react: '>=16.8.0' @@ -350,22 +253,13 @@ packages: optional: true '@emotion/serialize@1.3.3': - resolution: - { - integrity: sha512-EISGqt7sSNWHGI76hC7x1CksiXPahbxEOrC5RjmFRJTqLyEK9/9hZvBbiYn70dw4wuwMKiEMCUlR6ZXTSWQqxA== - } + resolution: {integrity: sha512-EISGqt7sSNWHGI76hC7x1CksiXPahbxEOrC5RjmFRJTqLyEK9/9hZvBbiYn70dw4wuwMKiEMCUlR6ZXTSWQqxA==} '@emotion/sheet@1.4.0': - resolution: - { - integrity: sha512-fTBW9/8r2w3dXWYM4HCB1Rdp8NLibOw2+XELH5m5+AkWiL/KqYX6dc0kKYlaYyKjrQ6ds33MCdMPEwgs2z1rqg== - } + resolution: {integrity: sha512-fTBW9/8r2w3dXWYM4HCB1Rdp8NLibOw2+XELH5m5+AkWiL/KqYX6dc0kKYlaYyKjrQ6ds33MCdMPEwgs2z1rqg==} '@emotion/styled@11.14.1': - resolution: - { - integrity: sha512-qEEJt42DuToa3gurlH4Qqc1kVpNq8wO8cJtDzU46TjlzWjDlsVyevtYCRijVq3SrHsROS+gVQ8Fnea108GnKzw== - } + resolution: {integrity: sha512-qEEJt42DuToa3gurlH4Qqc1kVpNq8wO8cJtDzU46TjlzWjDlsVyevtYCRijVq3SrHsROS+gVQ8Fnea108GnKzw==} peerDependencies: '@emotion/react': ^11.0.0-rc.0 '@types/react': '*' @@ -375,317 +269,206 @@ packages: optional: true '@emotion/unitless@0.10.0': - resolution: - { - integrity: sha512-dFoMUuQA20zvtVTuxZww6OHoJYgrzfKM1t52mVySDJnMSEa08ruEvdYQbhvyu6soU+NeLVd3yKfTfT0NeV6qGg== - } + resolution: {integrity: sha512-dFoMUuQA20zvtVTuxZww6OHoJYgrzfKM1t52mVySDJnMSEa08ruEvdYQbhvyu6soU+NeLVd3yKfTfT0NeV6qGg==} '@emotion/use-insertion-effect-with-fallbacks@1.2.0': - resolution: - { - integrity: sha512-yJMtVdH59sxi/aVJBpk9FQq+OR8ll5GT8oWd57UpeaKEVGab41JWaCFA7FRLoMLloOZF/c/wsPoe+bfGmRKgDg== - } + resolution: {integrity: sha512-yJMtVdH59sxi/aVJBpk9FQq+OR8ll5GT8oWd57UpeaKEVGab41JWaCFA7FRLoMLloOZF/c/wsPoe+bfGmRKgDg==} peerDependencies: react: '>=16.8.0' '@emotion/utils@1.4.2': - resolution: - { - integrity: sha512-3vLclRofFziIa3J2wDh9jjbkUz9qk5Vi3IZ/FSTKViB0k+ef0fPV7dYrUIugbgupYDx7v9ud/SjrtEP8Y4xLoA== - } + resolution: {integrity: sha512-3vLclRofFziIa3J2wDh9jjbkUz9qk5Vi3IZ/FSTKViB0k+ef0fPV7dYrUIugbgupYDx7v9ud/SjrtEP8Y4xLoA==} '@emotion/weak-memoize@0.4.0': - resolution: - { - integrity: sha512-snKqtPW01tN0ui7yu9rGv69aJXr/a/Ywvl11sUjNtEcRc+ng/mQriFL0wLXMef74iHa/EkftbDzU9F8iFbH+zg== - } + resolution: {integrity: sha512-snKqtPW01tN0ui7yu9rGv69aJXr/a/Ywvl11sUjNtEcRc+ng/mQriFL0wLXMef74iHa/EkftbDzU9F8iFbH+zg==} '@esbuild/aix-ppc64@0.27.4': - resolution: - { - integrity: sha512-cQPwL2mp2nSmHHJlCyoXgHGhbEPMrEEU5xhkcy3Hs/O7nGZqEpZ2sUtLaL9MORLtDfRvVl2/3PAuEkYZH0Ty8Q== - } - engines: { node: '>=18' } + resolution: {integrity: sha512-cQPwL2mp2nSmHHJlCyoXgHGhbEPMrEEU5xhkcy3Hs/O7nGZqEpZ2sUtLaL9MORLtDfRvVl2/3PAuEkYZH0Ty8Q==} + engines: {node: '>=18'} cpu: [ppc64] os: [aix] '@esbuild/android-arm64@0.27.4': - resolution: - { - integrity: sha512-gdLscB7v75wRfu7QSm/zg6Rx29VLdy9eTr2t44sfTW7CxwAtQghZ4ZnqHk3/ogz7xao0QAgrkradbBzcqFPasw== - } - engines: { node: '>=18' } + resolution: {integrity: sha512-gdLscB7v75wRfu7QSm/zg6Rx29VLdy9eTr2t44sfTW7CxwAtQghZ4ZnqHk3/ogz7xao0QAgrkradbBzcqFPasw==} + engines: {node: '>=18'} cpu: [arm64] os: [android] '@esbuild/android-arm@0.27.4': - resolution: - { - integrity: sha512-X9bUgvxiC8CHAGKYufLIHGXPJWnr0OCdR0anD2e21vdvgCI8lIfqFbnoeOz7lBjdrAGUhqLZLcQo6MLhTO2DKQ== - } - engines: { node: '>=18' } + resolution: {integrity: sha512-X9bUgvxiC8CHAGKYufLIHGXPJWnr0OCdR0anD2e21vdvgCI8lIfqFbnoeOz7lBjdrAGUhqLZLcQo6MLhTO2DKQ==} + engines: {node: '>=18'} cpu: [arm] os: [android] '@esbuild/android-x64@0.27.4': - resolution: - { - integrity: sha512-PzPFnBNVF292sfpfhiyiXCGSn9HZg5BcAz+ivBuSsl6Rk4ga1oEXAamhOXRFyMcjwr2DVtm40G65N3GLeH1Lvw== - } - engines: { node: '>=18' } + resolution: {integrity: sha512-PzPFnBNVF292sfpfhiyiXCGSn9HZg5BcAz+ivBuSsl6Rk4ga1oEXAamhOXRFyMcjwr2DVtm40G65N3GLeH1Lvw==} + engines: {node: '>=18'} cpu: [x64] os: [android] '@esbuild/darwin-arm64@0.27.4': - resolution: - { - integrity: sha512-b7xaGIwdJlht8ZFCvMkpDN6uiSmnxxK56N2GDTMYPr2/gzvfdQN8rTfBsvVKmIVY/X7EM+/hJKEIbbHs9oA4tQ== - } - engines: { node: '>=18' } + resolution: {integrity: sha512-b7xaGIwdJlht8ZFCvMkpDN6uiSmnxxK56N2GDTMYPr2/gzvfdQN8rTfBsvVKmIVY/X7EM+/hJKEIbbHs9oA4tQ==} + engines: {node: '>=18'} cpu: [arm64] os: [darwin] '@esbuild/darwin-x64@0.27.4': - resolution: - { - integrity: sha512-sR+OiKLwd15nmCdqpXMnuJ9W2kpy0KigzqScqHI3Hqwr7IXxBp3Yva+yJwoqh7rE8V77tdoheRYataNKL4QrPw== - } - engines: { node: '>=18' } + resolution: {integrity: sha512-sR+OiKLwd15nmCdqpXMnuJ9W2kpy0KigzqScqHI3Hqwr7IXxBp3Yva+yJwoqh7rE8V77tdoheRYataNKL4QrPw==} + engines: {node: '>=18'} cpu: [x64] os: [darwin] '@esbuild/freebsd-arm64@0.27.4': - resolution: - { - integrity: sha512-jnfpKe+p79tCnm4GVav68A7tUFeKQwQyLgESwEAUzyxk/TJr4QdGog9sqWNcUbr/bZt/O/HXouspuQDd9JxFSw== - } - engines: { node: '>=18' } + resolution: {integrity: sha512-jnfpKe+p79tCnm4GVav68A7tUFeKQwQyLgESwEAUzyxk/TJr4QdGog9sqWNcUbr/bZt/O/HXouspuQDd9JxFSw==} + engines: {node: '>=18'} cpu: [arm64] os: [freebsd] '@esbuild/freebsd-x64@0.27.4': - resolution: - { - integrity: sha512-2kb4ceA/CpfUrIcTUl1wrP/9ad9Atrp5J94Lq69w7UwOMolPIGrfLSvAKJp0RTvkPPyn6CIWrNy13kyLikZRZQ== - } - engines: { node: '>=18' } + resolution: {integrity: sha512-2kb4ceA/CpfUrIcTUl1wrP/9ad9Atrp5J94Lq69w7UwOMolPIGrfLSvAKJp0RTvkPPyn6CIWrNy13kyLikZRZQ==} + engines: {node: '>=18'} cpu: [x64] os: [freebsd] '@esbuild/linux-arm64@0.27.4': - resolution: - { - integrity: sha512-7nQOttdzVGth1iz57kxg9uCz57dxQLHWxopL6mYuYthohPKEK0vU0C3O21CcBK6KDlkYVcnDXY099HcCDXd9dA== - } - engines: { node: '>=18' } + resolution: {integrity: sha512-7nQOttdzVGth1iz57kxg9uCz57dxQLHWxopL6mYuYthohPKEK0vU0C3O21CcBK6KDlkYVcnDXY099HcCDXd9dA==} + engines: {node: '>=18'} cpu: [arm64] os: [linux] '@esbuild/linux-arm@0.27.4': - resolution: - { - integrity: sha512-aBYgcIxX/wd5n2ys0yESGeYMGF+pv6g0DhZr3G1ZG4jMfruU9Tl1i2Z+Wnj9/KjGz1lTLCcorqE2viePZqj4Eg== - } - engines: { node: '>=18' } + resolution: {integrity: sha512-aBYgcIxX/wd5n2ys0yESGeYMGF+pv6g0DhZr3G1ZG4jMfruU9Tl1i2Z+Wnj9/KjGz1lTLCcorqE2viePZqj4Eg==} + engines: {node: '>=18'} cpu: [arm] os: [linux] '@esbuild/linux-ia32@0.27.4': - resolution: - { - integrity: sha512-oPtixtAIzgvzYcKBQM/qZ3R+9TEUd1aNJQu0HhGyqtx6oS7qTpvjheIWBbes4+qu1bNlo2V4cbkISr8q6gRBFA== - } - engines: { node: '>=18' } + resolution: {integrity: sha512-oPtixtAIzgvzYcKBQM/qZ3R+9TEUd1aNJQu0HhGyqtx6oS7qTpvjheIWBbes4+qu1bNlo2V4cbkISr8q6gRBFA==} + engines: {node: '>=18'} cpu: [ia32] os: [linux] '@esbuild/linux-loong64@0.14.54': - resolution: - { - integrity: sha512-bZBrLAIX1kpWelV0XemxBZllyRmM6vgFQQG2GdNb+r3Fkp0FOh1NJSvekXDs7jq70k4euu1cryLMfU+mTXlEpw== - } - engines: { node: '>=12' } + resolution: {integrity: sha512-bZBrLAIX1kpWelV0XemxBZllyRmM6vgFQQG2GdNb+r3Fkp0FOh1NJSvekXDs7jq70k4euu1cryLMfU+mTXlEpw==} + engines: {node: '>=12'} cpu: [loong64] os: [linux] '@esbuild/linux-loong64@0.27.4': - resolution: - { - integrity: sha512-8mL/vh8qeCoRcFH2nM8wm5uJP+ZcVYGGayMavi8GmRJjuI3g1v6Z7Ni0JJKAJW+m0EtUuARb6Lmp4hMjzCBWzA== - } - engines: { node: '>=18' } + resolution: {integrity: sha512-8mL/vh8qeCoRcFH2nM8wm5uJP+ZcVYGGayMavi8GmRJjuI3g1v6Z7Ni0JJKAJW+m0EtUuARb6Lmp4hMjzCBWzA==} + engines: {node: '>=18'} cpu: [loong64] os: [linux] '@esbuild/linux-mips64el@0.27.4': - resolution: - { - integrity: sha512-1RdrWFFiiLIW7LQq9Q2NES+HiD4NyT8Itj9AUeCl0IVCA459WnPhREKgwrpaIfTOe+/2rdntisegiPWn/r/aAw== - } - engines: { node: '>=18' } + resolution: {integrity: sha512-1RdrWFFiiLIW7LQq9Q2NES+HiD4NyT8Itj9AUeCl0IVCA459WnPhREKgwrpaIfTOe+/2rdntisegiPWn/r/aAw==} + engines: {node: '>=18'} cpu: [mips64el] os: [linux] '@esbuild/linux-ppc64@0.27.4': - resolution: - { - integrity: sha512-tLCwNG47l3sd9lpfyx9LAGEGItCUeRCWeAx6x2Jmbav65nAwoPXfewtAdtbtit/pJFLUWOhpv0FpS6GQAmPrHA== - } - engines: { node: '>=18' } + resolution: {integrity: sha512-tLCwNG47l3sd9lpfyx9LAGEGItCUeRCWeAx6x2Jmbav65nAwoPXfewtAdtbtit/pJFLUWOhpv0FpS6GQAmPrHA==} + engines: {node: '>=18'} cpu: [ppc64] os: [linux] '@esbuild/linux-riscv64@0.27.4': - resolution: - { - integrity: sha512-BnASypppbUWyqjd1KIpU4AUBiIhVr6YlHx/cnPgqEkNoVOhHg+YiSVxM1RLfiy4t9cAulbRGTNCKOcqHrEQLIw== - } - engines: { node: '>=18' } + resolution: {integrity: sha512-BnASypppbUWyqjd1KIpU4AUBiIhVr6YlHx/cnPgqEkNoVOhHg+YiSVxM1RLfiy4t9cAulbRGTNCKOcqHrEQLIw==} + engines: {node: '>=18'} cpu: [riscv64] os: [linux] '@esbuild/linux-s390x@0.27.4': - resolution: - { - integrity: sha512-+eUqgb/Z7vxVLezG8bVB9SfBie89gMueS+I0xYh2tJdw3vqA/0ImZJ2ROeWwVJN59ihBeZ7Tu92dF/5dy5FttA== - } - engines: { node: '>=18' } + resolution: {integrity: sha512-+eUqgb/Z7vxVLezG8bVB9SfBie89gMueS+I0xYh2tJdw3vqA/0ImZJ2ROeWwVJN59ihBeZ7Tu92dF/5dy5FttA==} + engines: {node: '>=18'} cpu: [s390x] os: [linux] '@esbuild/linux-x64@0.27.4': - resolution: - { - integrity: sha512-S5qOXrKV8BQEzJPVxAwnryi2+Iq5pB40gTEIT69BQONqR7JH1EPIcQ/Uiv9mCnn05jff9umq/5nqzxlqTOg9NA== - } - engines: { node: '>=18' } + resolution: {integrity: sha512-S5qOXrKV8BQEzJPVxAwnryi2+Iq5pB40gTEIT69BQONqR7JH1EPIcQ/Uiv9mCnn05jff9umq/5nqzxlqTOg9NA==} + engines: {node: '>=18'} cpu: [x64] os: [linux] '@esbuild/netbsd-arm64@0.27.4': - resolution: - { - integrity: sha512-xHT8X4sb0GS8qTqiwzHqpY00C95DPAq7nAwX35Ie/s+LO9830hrMd3oX0ZMKLvy7vsonee73x0lmcdOVXFzd6Q== - } - engines: { node: '>=18' } + resolution: {integrity: sha512-xHT8X4sb0GS8qTqiwzHqpY00C95DPAq7nAwX35Ie/s+LO9830hrMd3oX0ZMKLvy7vsonee73x0lmcdOVXFzd6Q==} + engines: {node: '>=18'} cpu: [arm64] os: [netbsd] '@esbuild/netbsd-x64@0.27.4': - resolution: - { - integrity: sha512-RugOvOdXfdyi5Tyv40kgQnI0byv66BFgAqjdgtAKqHoZTbTF2QqfQrFwa7cHEORJf6X2ht+l9ABLMP0dnKYsgg== - } - engines: { node: '>=18' } + resolution: {integrity: sha512-RugOvOdXfdyi5Tyv40kgQnI0byv66BFgAqjdgtAKqHoZTbTF2QqfQrFwa7cHEORJf6X2ht+l9ABLMP0dnKYsgg==} + engines: {node: '>=18'} cpu: [x64] os: [netbsd] '@esbuild/openbsd-arm64@0.27.4': - resolution: - { - integrity: sha512-2MyL3IAaTX+1/qP0O1SwskwcwCoOI4kV2IBX1xYnDDqthmq5ArrW94qSIKCAuRraMgPOmG0RDTA74mzYNQA9ow== - } - engines: { node: '>=18' } + resolution: {integrity: sha512-2MyL3IAaTX+1/qP0O1SwskwcwCoOI4kV2IBX1xYnDDqthmq5ArrW94qSIKCAuRraMgPOmG0RDTA74mzYNQA9ow==} + engines: {node: '>=18'} cpu: [arm64] os: [openbsd] '@esbuild/openbsd-x64@0.27.4': - resolution: - { - integrity: sha512-u8fg/jQ5aQDfsnIV6+KwLOf1CmJnfu1ShpwqdwC0uA7ZPwFws55Ngc12vBdeUdnuWoQYx/SOQLGDcdlfXhYmXQ== - } - engines: { node: '>=18' } + resolution: {integrity: sha512-u8fg/jQ5aQDfsnIV6+KwLOf1CmJnfu1ShpwqdwC0uA7ZPwFws55Ngc12vBdeUdnuWoQYx/SOQLGDcdlfXhYmXQ==} + engines: {node: '>=18'} cpu: [x64] os: [openbsd] '@esbuild/openharmony-arm64@0.27.4': - resolution: - { - integrity: sha512-JkTZrl6VbyO8lDQO3yv26nNr2RM2yZzNrNHEsj9bm6dOwwu9OYN28CjzZkH57bh4w0I2F7IodpQvUAEd1mbWXg== - } - engines: { node: '>=18' } + resolution: {integrity: sha512-JkTZrl6VbyO8lDQO3yv26nNr2RM2yZzNrNHEsj9bm6dOwwu9OYN28CjzZkH57bh4w0I2F7IodpQvUAEd1mbWXg==} + engines: {node: '>=18'} cpu: [arm64] os: [openharmony] '@esbuild/sunos-x64@0.27.4': - resolution: - { - integrity: sha512-/gOzgaewZJfeJTlsWhvUEmUG4tWEY2Spp5M20INYRg2ZKl9QPO3QEEgPeRtLjEWSW8FilRNacPOg8R1uaYkA6g== - } - engines: { node: '>=18' } + resolution: {integrity: sha512-/gOzgaewZJfeJTlsWhvUEmUG4tWEY2Spp5M20INYRg2ZKl9QPO3QEEgPeRtLjEWSW8FilRNacPOg8R1uaYkA6g==} + engines: {node: '>=18'} cpu: [x64] os: [sunos] '@esbuild/win32-arm64@0.27.4': - resolution: - { - integrity: sha512-Z9SExBg2y32smoDQdf1HRwHRt6vAHLXcxD2uGgO/v2jK7Y718Ix4ndsbNMU/+1Qiem9OiOdaqitioZwxivhXYg== - } - engines: { node: '>=18' } + resolution: {integrity: sha512-Z9SExBg2y32smoDQdf1HRwHRt6vAHLXcxD2uGgO/v2jK7Y718Ix4ndsbNMU/+1Qiem9OiOdaqitioZwxivhXYg==} + engines: {node: '>=18'} cpu: [arm64] os: [win32] '@esbuild/win32-ia32@0.27.4': - resolution: - { - integrity: sha512-DAyGLS0Jz5G5iixEbMHi5KdiApqHBWMGzTtMiJ72ZOLhbu/bzxgAe8Ue8CTS3n3HbIUHQz/L51yMdGMeoxXNJw== - } - engines: { node: '>=18' } + resolution: {integrity: sha512-DAyGLS0Jz5G5iixEbMHi5KdiApqHBWMGzTtMiJ72ZOLhbu/bzxgAe8Ue8CTS3n3HbIUHQz/L51yMdGMeoxXNJw==} + engines: {node: '>=18'} cpu: [ia32] os: [win32] '@esbuild/win32-x64@0.27.4': - resolution: - { - integrity: sha512-+knoa0BDoeXgkNvvV1vvbZX4+hizelrkwmGJBdT17t8FNPwG2lKemmuMZlmaNQ3ws3DKKCxpb4zRZEIp3UxFCg== - } - engines: { node: '>=18' } + resolution: {integrity: sha512-+knoa0BDoeXgkNvvV1vvbZX4+hizelrkwmGJBdT17t8FNPwG2lKemmuMZlmaNQ3ws3DKKCxpb4zRZEIp3UxFCg==} + engines: {node: '>=18'} cpu: [x64] os: [win32] '@eslint-community/eslint-utils@4.9.1': - resolution: - { - integrity: sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ== - } - engines: { node: ^12.22.0 || ^14.17.0 || >=16.0.0 } + resolution: {integrity: sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} peerDependencies: eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 '@eslint-community/regexpp@4.12.2': - resolution: - { - integrity: sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew== - } - engines: { node: ^12.0.0 || ^14.0.0 || >=16.0.0 } + resolution: {integrity: sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==} + engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} '@eslint/config-array@0.23.5': - resolution: - { - integrity: sha512-Y3kKLvC1dvTOT+oGlqNQ1XLqK6D1HU2YXPc52NmAlJZbMMWDzGYXMiPRJ8TYD39muD/OTjlZmNJ4ib7dvSrMBA== - } - engines: { node: ^20.19.0 || ^22.13.0 || >=24 } + resolution: {integrity: sha512-Y3kKLvC1dvTOT+oGlqNQ1XLqK6D1HU2YXPc52NmAlJZbMMWDzGYXMiPRJ8TYD39muD/OTjlZmNJ4ib7dvSrMBA==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} '@eslint/config-helpers@0.5.5': - resolution: - { - integrity: sha512-eIJYKTCECbP/nsKaaruF6LW967mtbQbsw4JTtSVkUQc9MneSkbrgPJAbKl9nWr0ZeowV8BfsarBmPpBzGelA2w== - } - engines: { node: ^20.19.0 || ^22.13.0 || >=24 } + resolution: {integrity: sha512-eIJYKTCECbP/nsKaaruF6LW967mtbQbsw4JTtSVkUQc9MneSkbrgPJAbKl9nWr0ZeowV8BfsarBmPpBzGelA2w==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} '@eslint/core@1.2.1': - resolution: - { - integrity: sha512-MwcE1P+AZ4C6DWlpin/OmOA54mmIZ/+xZuJiQd4SyB29oAJjN30UW9wkKNptW2ctp4cEsvhlLY/CsQ1uoHDloQ== - } - engines: { node: ^20.19.0 || ^22.13.0 || >=24 } + resolution: {integrity: sha512-MwcE1P+AZ4C6DWlpin/OmOA54mmIZ/+xZuJiQd4SyB29oAJjN30UW9wkKNptW2ctp4cEsvhlLY/CsQ1uoHDloQ==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} '@eslint/js@10.0.1': - resolution: - { - integrity: sha512-zeR9k5pd4gxjZ0abRoIaxdc7I3nDktoXZk2qOv9gCNWx3mVwEn32VRhyLaRsDiJjTs0xq/T8mfPtyuXu7GWBcA== - } - engines: { node: ^20.19.0 || ^22.13.0 || >=24 } + resolution: {integrity: sha512-zeR9k5pd4gxjZ0abRoIaxdc7I3nDktoXZk2qOv9gCNWx3mVwEn32VRhyLaRsDiJjTs0xq/T8mfPtyuXu7GWBcA==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} peerDependencies: eslint: ^10.0.0 peerDependenciesMeta: @@ -693,103 +476,58 @@ packages: optional: true '@eslint/object-schema@3.0.5': - resolution: - { - integrity: sha512-vqTaUEgxzm+YDSdElad6PiRoX4t8VGDjCtt05zn4nU810UIx/uNEV7/lZJ6KwFThKZOzOxzXy48da+No7HZaMw== - } - engines: { node: ^20.19.0 || ^22.13.0 || >=24 } + resolution: {integrity: sha512-vqTaUEgxzm+YDSdElad6PiRoX4t8VGDjCtt05zn4nU810UIx/uNEV7/lZJ6KwFThKZOzOxzXy48da+No7HZaMw==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} '@eslint/plugin-kit@0.7.1': - resolution: - { - integrity: sha512-rZAP3aVgB9ds9KOeUSL+zZ21hPmo8dh6fnIFwRQj5EAZl9gzR7wxYbYXYysAM8CTqGmUGyp2S4kUdV17MnGuWQ== - } - engines: { node: ^20.19.0 || ^22.13.0 || >=24 } + resolution: {integrity: sha512-rZAP3aVgB9ds9KOeUSL+zZ21hPmo8dh6fnIFwRQj5EAZl9gzR7wxYbYXYysAM8CTqGmUGyp2S4kUdV17MnGuWQ==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} '@humanfs/core@0.19.2': - resolution: - { - integrity: sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA== - } - engines: { node: '>=18.18.0' } + resolution: {integrity: sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA==} + engines: {node: '>=18.18.0'} '@humanfs/node@0.16.8': - resolution: - { - integrity: sha512-gE1eQNZ3R++kTzFUpdGlpmy8kDZD/MLyHqDwqjkVQI0JMdI1D51sy1H958PNXYkM2rAac7e5/CnIKZrHtPh3BQ== - } - engines: { node: '>=18.18.0' } + resolution: {integrity: sha512-gE1eQNZ3R++kTzFUpdGlpmy8kDZD/MLyHqDwqjkVQI0JMdI1D51sy1H958PNXYkM2rAac7e5/CnIKZrHtPh3BQ==} + engines: {node: '>=18.18.0'} '@humanfs/types@0.15.0': - resolution: - { - integrity: sha512-ZZ1w0aoQkwuUuC7Yf+7sdeaNfqQiiLcSRbfI08oAxqLtpXQr9AIVX7Ay7HLDuiLYAaFPu8oBYNq/QIi9URHJ3Q== - } - engines: { node: '>=18.18.0' } + resolution: {integrity: sha512-ZZ1w0aoQkwuUuC7Yf+7sdeaNfqQiiLcSRbfI08oAxqLtpXQr9AIVX7Ay7HLDuiLYAaFPu8oBYNq/QIi9URHJ3Q==} + engines: {node: '>=18.18.0'} '@humanwhocodes/module-importer@1.0.1': - resolution: - { - integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA== - } - engines: { node: '>=12.22' } + resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==} + engines: {node: '>=12.22'} '@humanwhocodes/retry@0.4.3': - resolution: - { - integrity: sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ== - } - engines: { node: '>=18.18' } + resolution: {integrity: sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==} + engines: {node: '>=18.18'} '@jridgewell/gen-mapping@0.3.13': - resolution: - { - integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA== - } + resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} '@jridgewell/remapping@2.3.5': - resolution: - { - integrity: sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ== - } + resolution: {integrity: sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==} '@jridgewell/resolve-uri@3.1.2': - resolution: - { - integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw== - } - engines: { node: '>=6.0.0' } + resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} + engines: {node: '>=6.0.0'} '@jridgewell/source-map@0.3.11': - resolution: - { - integrity: sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA== - } + resolution: {integrity: sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA==} '@jridgewell/sourcemap-codec@1.5.5': - resolution: - { - integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og== - } + resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} '@jridgewell/trace-mapping@0.3.31': - resolution: - { - integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw== - } + resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} '@mui/core-downloads-tracker@9.0.0': - resolution: - { - integrity: sha512-uwQNGkhv0lf7ufxw6QXev77BW6pWbW+7uxYjU5+rfp4lBkFtMEgJCsarTM3Tn+i0lGx6+Ol2u88JdGXr0GDskA== - } + resolution: {integrity: sha512-uwQNGkhv0lf7ufxw6QXev77BW6pWbW+7uxYjU5+rfp4lBkFtMEgJCsarTM3Tn+i0lGx6+Ol2u88JdGXr0GDskA==} '@mui/icons-material@9.0.0': - resolution: - { - integrity: sha512-oDwyvI6LgjWRC9MBcSGvLkPud9S9ELgSBQFYxa1rYcZn6Br55dn22SyvsPDMsn0G8OndFk53iMT45W5mNqrogw== - } - engines: { node: '>=14.0.0' } + resolution: {integrity: sha512-oDwyvI6LgjWRC9MBcSGvLkPud9S9ELgSBQFYxa1rYcZn6Br55dn22SyvsPDMsn0G8OndFk53iMT45W5mNqrogw==} + engines: {node: '>=14.0.0'} peerDependencies: '@mui/material': ^9.0.0 '@types/react': ^17.0.0 || ^18.0.0 || ^19.0.0 @@ -799,11 +537,8 @@ packages: optional: true '@mui/material@9.0.0': - resolution: - { - integrity: sha512-+VP/oQCDhDR87NQQgXnNBG8dwy6GNuQLnenS1pZvkbn2dKFSxRSRMybTpH9xUxXP+316mlYDy5CSbYtusnCWtw== - } - engines: { node: '>=14.0.0' } + resolution: {integrity: sha512-+VP/oQCDhDR87NQQgXnNBG8dwy6GNuQLnenS1pZvkbn2dKFSxRSRMybTpH9xUxXP+316mlYDy5CSbYtusnCWtw==} + engines: {node: '>=14.0.0'} peerDependencies: '@emotion/react': ^11.5.0 '@emotion/styled': ^11.3.0 @@ -822,11 +557,8 @@ packages: optional: true '@mui/private-theming@9.0.0': - resolution: - { - integrity: sha512-JtuZoaiCqwD6vjgYu6Xp3T7DZkrxJlgtDz5yESzhI34fEX5hHMh2VJUbuL9UOg8xrfIFMrq6dcYoH/7Zi4G0RA== - } - engines: { node: '>=14.0.0' } + resolution: {integrity: sha512-JtuZoaiCqwD6vjgYu6Xp3T7DZkrxJlgtDz5yESzhI34fEX5hHMh2VJUbuL9UOg8xrfIFMrq6dcYoH/7Zi4G0RA==} + engines: {node: '>=14.0.0'} peerDependencies: '@types/react': ^17.0.0 || ^18.0.0 || ^19.0.0 react: ^17.0.0 || ^18.0.0 || ^19.0.0 @@ -835,11 +567,8 @@ packages: optional: true '@mui/styled-engine@9.0.0': - resolution: - { - integrity: sha512-9RLGdX4Jg0aQPRuvqh/OLzYSPlgd5zyEw5/1HIRfdavSiOd03WtUaGZH9/w1RoTYuRKwpgy0hpIFaMHIqPVIWg== - } - engines: { node: '>=14.0.0' } + resolution: {integrity: sha512-9RLGdX4Jg0aQPRuvqh/OLzYSPlgd5zyEw5/1HIRfdavSiOd03WtUaGZH9/w1RoTYuRKwpgy0hpIFaMHIqPVIWg==} + engines: {node: '>=14.0.0'} peerDependencies: '@emotion/react': ^11.4.1 '@emotion/styled': ^11.3.0 @@ -851,11 +580,8 @@ packages: optional: true '@mui/system@9.0.0': - resolution: - { - integrity: sha512-YnC5Zg6j04IxiLc/boAKs0464jfZlLFVa7mf5E8lF0XOtZVUvG6R6gJK50lgUYdaaLdyLfxF6xR7LaPuEpeT/g== - } - engines: { node: '>=14.0.0' } + resolution: {integrity: sha512-YnC5Zg6j04IxiLc/boAKs0464jfZlLFVa7mf5E8lF0XOtZVUvG6R6gJK50lgUYdaaLdyLfxF6xR7LaPuEpeT/g==} + engines: {node: '>=14.0.0'} peerDependencies: '@emotion/react': ^11.5.0 '@emotion/styled': ^11.3.0 @@ -870,10 +596,7 @@ packages: optional: true '@mui/types@9.0.0': - resolution: - { - integrity: sha512-i1cuFCAWN44b3AJWO7mh7tuh1sqbQSeVr/94oG0TX5uXivac8XalgE4/6fQZcmGZigzbQ35IXxj/4jLpRIBYZg== - } + resolution: {integrity: sha512-i1cuFCAWN44b3AJWO7mh7tuh1sqbQSeVr/94oG0TX5uXivac8XalgE4/6fQZcmGZigzbQ35IXxj/4jLpRIBYZg==} peerDependencies: '@types/react': ^17.0.0 || ^18.0.0 || ^19.0.0 peerDependenciesMeta: @@ -881,11 +604,8 @@ packages: optional: true '@mui/utils@9.0.0': - resolution: - { - integrity: sha512-bQcqyg/gjULUqTuyUjSAFr6LQGLvtkNtDbJerAtoUn9kGZ0hg5QJiN1PLHMLbeFpe3te1831uq7GFl2ITokGdg== - } - engines: { node: '>=14.0.0' } + resolution: {integrity: sha512-bQcqyg/gjULUqTuyUjSAFr6LQGLvtkNtDbJerAtoUn9kGZ0hg5QJiN1PLHMLbeFpe3te1831uq7GFl2ITokGdg==} + engines: {node: '>=14.0.0'} peerDependencies: '@types/react': ^17.0.0 || ^18.0.0 || ^19.0.0 react: ^17.0.0 || ^18.0.0 || ^19.0.0 @@ -894,244 +614,157 @@ packages: optional: true '@napi-rs/wasm-runtime@1.1.4': - resolution: - { - integrity: sha512-3NQNNgA1YSlJb/kMH1ildASP9HW7/7kYnRI2szWJaofaS1hWmbGI4H+d3+22aGzXXN9IJ+n+GiFVcGipJP18ow== - } + resolution: {integrity: sha512-3NQNNgA1YSlJb/kMH1ildASP9HW7/7kYnRI2szWJaofaS1hWmbGI4H+d3+22aGzXXN9IJ+n+GiFVcGipJP18ow==} peerDependencies: '@emnapi/core': ^1.7.1 '@emnapi/runtime': ^1.7.1 '@nodelib/fs.scandir@2.1.5': - resolution: - { - integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g== - } - engines: { node: '>= 8' } + resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} + engines: {node: '>= 8'} '@nodelib/fs.stat@2.0.5': - resolution: - { - integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A== - } - engines: { node: '>= 8' } + resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==} + engines: {node: '>= 8'} '@nodelib/fs.walk@1.2.8': - resolution: - { - integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg== - } - engines: { node: '>= 8' } + resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} + engines: {node: '>= 8'} '@oxc-project/types@0.127.0': - resolution: - { - integrity: sha512-aIYXQBo4lCbO4z0R3FHeucQHpF46l2LbMdxRvqvuRuW2OxdnSkcng5B8+K12spgLDj93rtN3+J2Vac/TIO+ciQ== - } + resolution: {integrity: sha512-aIYXQBo4lCbO4z0R3FHeucQHpF46l2LbMdxRvqvuRuW2OxdnSkcng5B8+K12spgLDj93rtN3+J2Vac/TIO+ciQ==} '@popperjs/core@2.11.8': - resolution: - { - integrity: sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A== - } + resolution: {integrity: sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==} '@preact/preset-vite@2.10.5': - resolution: - { - integrity: sha512-p0vJpxiVO7KWWazWny3LUZ+saXyZKWv6Ju0bYMWNJRp2YveufRPgSUB1C4MTqGJfz07EehMgfN+AJNwQy+w6Iw== - } + resolution: {integrity: sha512-p0vJpxiVO7KWWazWny3LUZ+saXyZKWv6Ju0bYMWNJRp2YveufRPgSUB1C4MTqGJfz07EehMgfN+AJNwQy+w6Iw==} peerDependencies: '@babel/core': 7.x vite: 2.x || 3.x || 4.x || 5.x || 6.x || 7.x || 8.x '@prefresh/babel-plugin@0.5.3': - resolution: - { - integrity: sha512-57LX2SHs4BX2s1IwCjNzTE2OJeEepRCNf1VTEpbNcUyHfMO68eeOWGDIt4ob9aYlW6PEWZ1SuwNikuoIXANDtQ== - } + resolution: {integrity: sha512-57LX2SHs4BX2s1IwCjNzTE2OJeEepRCNf1VTEpbNcUyHfMO68eeOWGDIt4ob9aYlW6PEWZ1SuwNikuoIXANDtQ==} '@prefresh/core@1.5.9': - resolution: - { - integrity: sha512-IKBKCPaz34OFVC+adiQ2qaTF5qdztO2/4ZPf4KsRTgjKosWqxVXmEbxCiUydYZRY8GVie+DQlKzQr9gt6HQ+EQ== - } + resolution: {integrity: sha512-IKBKCPaz34OFVC+adiQ2qaTF5qdztO2/4ZPf4KsRTgjKosWqxVXmEbxCiUydYZRY8GVie+DQlKzQr9gt6HQ+EQ==} peerDependencies: preact: ^10.0.0 || ^11.0.0-0 '@prefresh/utils@1.2.1': - resolution: - { - integrity: sha512-vq/sIuN5nYfYzvyayXI4C2QkprfNaHUQ9ZX+3xLD8nL3rWyzpxOm1+K7RtMbhd+66QcaISViK7amjnheQ/4WZw== - } + resolution: {integrity: sha512-vq/sIuN5nYfYzvyayXI4C2QkprfNaHUQ9ZX+3xLD8nL3rWyzpxOm1+K7RtMbhd+66QcaISViK7amjnheQ/4WZw==} '@prefresh/vite@2.4.12': - resolution: - { - integrity: sha512-FY1fzXpUjiuosznMV0YM7XAOPZjB5FIdWS0W24+XnlxYkt9hNAwwsiKYn+cuTEoMtD/ZVazS5QVssBr9YhpCQA== - } + resolution: {integrity: sha512-FY1fzXpUjiuosznMV0YM7XAOPZjB5FIdWS0W24+XnlxYkt9hNAwwsiKYn+cuTEoMtD/ZVazS5QVssBr9YhpCQA==} peerDependencies: preact: ^10.4.0 || ^11.0.0-0 vite: '>=2.0.0' '@rolldown/binding-android-arm64@1.0.0-rc.17': - resolution: - { - integrity: sha512-s70pVGhw4zqGeFnXWvAzJDlvxhlRollagdCCKRgOsgUOH3N1l0LIxf83AtGzmb5SiVM4Hjl5HyarMRfdfj3DaQ== - } - engines: { node: ^20.19.0 || >=22.12.0 } + resolution: {integrity: sha512-s70pVGhw4zqGeFnXWvAzJDlvxhlRollagdCCKRgOsgUOH3N1l0LIxf83AtGzmb5SiVM4Hjl5HyarMRfdfj3DaQ==} + engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [android] '@rolldown/binding-darwin-arm64@1.0.0-rc.17': - resolution: - { - integrity: sha512-4ksWc9n0mhlZpZ9PMZgTGjeOPRu8MB1Z3Tz0Mo02eWfWCHMW1zN82Qz/pL/rC+yQa+8ZnutMF0JjJe7PjwasYw== - } - engines: { node: ^20.19.0 || >=22.12.0 } + resolution: {integrity: sha512-4ksWc9n0mhlZpZ9PMZgTGjeOPRu8MB1Z3Tz0Mo02eWfWCHMW1zN82Qz/pL/rC+yQa+8ZnutMF0JjJe7PjwasYw==} + engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [darwin] '@rolldown/binding-darwin-x64@1.0.0-rc.17': - resolution: - { - integrity: sha512-SUSDOI6WwUVNcWxd02QEBjLdY1VPHvlEkw6T/8nYG322iYWCTxRb1vzk4E+mWWYehTp7ERibq54LSJGjmouOsw== - } - engines: { node: ^20.19.0 || >=22.12.0 } + resolution: {integrity: sha512-SUSDOI6WwUVNcWxd02QEBjLdY1VPHvlEkw6T/8nYG322iYWCTxRb1vzk4E+mWWYehTp7ERibq54LSJGjmouOsw==} + engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [darwin] '@rolldown/binding-freebsd-x64@1.0.0-rc.17': - resolution: - { - integrity: sha512-hwnz3nw9dbJ05EDO/PvcjaaewqqDy7Y1rn1UO81l8iIK1GjenME75dl16ajbvSSMfv66WXSRCYKIqfgq2KCfxw== - } - engines: { node: ^20.19.0 || >=22.12.0 } + resolution: {integrity: sha512-hwnz3nw9dbJ05EDO/PvcjaaewqqDy7Y1rn1UO81l8iIK1GjenME75dl16ajbvSSMfv66WXSRCYKIqfgq2KCfxw==} + engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [freebsd] '@rolldown/binding-linux-arm-gnueabihf@1.0.0-rc.17': - resolution: - { - integrity: sha512-IS+W7epTcwANmFSQFrS1SivEXHtl1JtuQA9wlxrZTcNi6mx+FDOYrakGevvvTwgj2JvWiK8B29/qD9BELZPyXQ== - } - engines: { node: ^20.19.0 || >=22.12.0 } + resolution: {integrity: sha512-IS+W7epTcwANmFSQFrS1SivEXHtl1JtuQA9wlxrZTcNi6mx+FDOYrakGevvvTwgj2JvWiK8B29/qD9BELZPyXQ==} + engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm] os: [linux] '@rolldown/binding-linux-arm64-gnu@1.0.0-rc.17': - resolution: - { - integrity: sha512-e6usGaHKW5BMNZOymS1UcEYGowQMWcgZ71Z17Sl/h2+ZziNJ1a9n3Zvcz6LdRyIW5572wBCTH/Z+bKuZouGk9Q== - } - engines: { node: ^20.19.0 || >=22.12.0 } + resolution: {integrity: sha512-e6usGaHKW5BMNZOymS1UcEYGowQMWcgZ71Z17Sl/h2+ZziNJ1a9n3Zvcz6LdRyIW5572wBCTH/Z+bKuZouGk9Q==} + engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] libc: [glibc] '@rolldown/binding-linux-arm64-musl@1.0.0-rc.17': - resolution: - { - integrity: sha512-b/CgbwAJpmrRLp02RPfhbudf5tZnN9nsPWK82znefso832etkem8H7FSZwxrOI9djcdTP7U6YfNhbRnh7djErg== - } - engines: { node: ^20.19.0 || >=22.12.0 } + resolution: {integrity: sha512-b/CgbwAJpmrRLp02RPfhbudf5tZnN9nsPWK82znefso832etkem8H7FSZwxrOI9djcdTP7U6YfNhbRnh7djErg==} + engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] libc: [musl] '@rolldown/binding-linux-ppc64-gnu@1.0.0-rc.17': - resolution: - { - integrity: sha512-4EII1iNGRUN5WwGbF/kOh/EIkoDN9HsupgLQoXfY+D1oyJm7/F4t5PYU5n8SWZgG0FEwakyM8pGgwcBYruGTlA== - } - engines: { node: ^20.19.0 || >=22.12.0 } + resolution: {integrity: sha512-4EII1iNGRUN5WwGbF/kOh/EIkoDN9HsupgLQoXfY+D1oyJm7/F4t5PYU5n8SWZgG0FEwakyM8pGgwcBYruGTlA==} + engines: {node: ^20.19.0 || >=22.12.0} cpu: [ppc64] os: [linux] libc: [glibc] '@rolldown/binding-linux-s390x-gnu@1.0.0-rc.17': - resolution: - { - integrity: sha512-AH8oq3XqQo4IibpVXvPeLDI5pzkpYn0WiZAfT05kFzoJ6tQNzwRdDYQ45M8I/gslbodRZwW8uxLhbSBbkv96rA== - } - engines: { node: ^20.19.0 || >=22.12.0 } + resolution: {integrity: sha512-AH8oq3XqQo4IibpVXvPeLDI5pzkpYn0WiZAfT05kFzoJ6tQNzwRdDYQ45M8I/gslbodRZwW8uxLhbSBbkv96rA==} + engines: {node: ^20.19.0 || >=22.12.0} cpu: [s390x] os: [linux] libc: [glibc] '@rolldown/binding-linux-x64-gnu@1.0.0-rc.17': - resolution: - { - integrity: sha512-cLnjV3xfo7KslbU41Z7z8BH/E1y5mzUYzAqih1d1MDaIGZRCMqTijqLv76/P7fyHuvUcfGsIpqCdddbxLLK9rA== - } - engines: { node: ^20.19.0 || >=22.12.0 } + resolution: {integrity: sha512-cLnjV3xfo7KslbU41Z7z8BH/E1y5mzUYzAqih1d1MDaIGZRCMqTijqLv76/P7fyHuvUcfGsIpqCdddbxLLK9rA==} + engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] libc: [glibc] '@rolldown/binding-linux-x64-musl@1.0.0-rc.17': - resolution: - { - integrity: sha512-0phclDw1spsL7dUB37sIARuis2tAgomCJXAHZlpt8PXZ4Ba0dRP1e+66lsRqrfhISeN9bEGNjQs+T/Fbd7oYGw== - } - engines: { node: ^20.19.0 || >=22.12.0 } + resolution: {integrity: sha512-0phclDw1spsL7dUB37sIARuis2tAgomCJXAHZlpt8PXZ4Ba0dRP1e+66lsRqrfhISeN9bEGNjQs+T/Fbd7oYGw==} + engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] libc: [musl] '@rolldown/binding-openharmony-arm64@1.0.0-rc.17': - resolution: - { - integrity: sha512-0ag/hEgXOwgw4t8QyQvUCxvEg+V0KBcA6YuOx9g0r02MprutRF5dyljgm3EmR02O292UX7UeS6HzWHAl6KgyhA== - } - engines: { node: ^20.19.0 || >=22.12.0 } + resolution: {integrity: sha512-0ag/hEgXOwgw4t8QyQvUCxvEg+V0KBcA6YuOx9g0r02MprutRF5dyljgm3EmR02O292UX7UeS6HzWHAl6KgyhA==} + engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [openharmony] '@rolldown/binding-wasm32-wasi@1.0.0-rc.17': - resolution: - { - integrity: sha512-LEXei6vo0E5wTGwpkJ4KoT3OZJRnglwldt5ziLzOlc6qqb55z4tWNq2A+PFqCJuvWWdP53CVhG1Z9NtToDPJrA== - } - engines: { node: ^20.19.0 || >=22.12.0 } + resolution: {integrity: sha512-LEXei6vo0E5wTGwpkJ4KoT3OZJRnglwldt5ziLzOlc6qqb55z4tWNq2A+PFqCJuvWWdP53CVhG1Z9NtToDPJrA==} + engines: {node: ^20.19.0 || >=22.12.0} cpu: [wasm32] '@rolldown/binding-win32-arm64-msvc@1.0.0-rc.17': - resolution: - { - integrity: sha512-gUmyzBl3SPMa6hrqFUth9sVfcLBlYsbMzBx5PlexMroZStgzGqlZ26pYG89rBb45Mnia+oil6YAIFeEWGWhoZA== - } - engines: { node: ^20.19.0 || >=22.12.0 } + resolution: {integrity: sha512-gUmyzBl3SPMa6hrqFUth9sVfcLBlYsbMzBx5PlexMroZStgzGqlZ26pYG89rBb45Mnia+oil6YAIFeEWGWhoZA==} + engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [win32] '@rolldown/binding-win32-x64-msvc@1.0.0-rc.17': - resolution: - { - integrity: sha512-3hkiolcUAvPB9FLb3UZdfjVVNWherN1f/skkGWJP/fgSQhYUZpSIRr0/I8ZK9TkF3F7kxvJAk0+IcKvPHk9qQg== - } - engines: { node: ^20.19.0 || >=22.12.0 } + resolution: {integrity: sha512-3hkiolcUAvPB9FLb3UZdfjVVNWherN1f/skkGWJP/fgSQhYUZpSIRr0/I8ZK9TkF3F7kxvJAk0+IcKvPHk9qQg==} + engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [win32] '@rolldown/pluginutils@1.0.0-rc.17': - resolution: - { - integrity: sha512-n8iosDOt6Ig1UhJ2AYqoIhHWh/isz0xpicHTzpKBeotdVsTEcxsSA/i3EVM7gQAj0rU27OLAxCjzlj15IWY7bg== - } + resolution: {integrity: sha512-n8iosDOt6Ig1UhJ2AYqoIhHWh/isz0xpicHTzpKBeotdVsTEcxsSA/i3EVM7gQAj0rU27OLAxCjzlj15IWY7bg==} '@rollup/pluginutils@4.2.1': - resolution: - { - integrity: sha512-iKnFXr7NkdZAIHiIWE+BX5ULi/ucVFYWD6TbAV+rZctiRTY2PL6tsIKhoIOaoskiWAkgu+VsbXgUVDNLHf+InQ== - } - engines: { node: '>= 8.0.0' } + resolution: {integrity: sha512-iKnFXr7NkdZAIHiIWE+BX5ULi/ucVFYWD6TbAV+rZctiRTY2PL6tsIKhoIOaoskiWAkgu+VsbXgUVDNLHf+InQ==} + engines: {node: '>= 8.0.0'} '@rollup/pluginutils@5.3.0': - resolution: - { - integrity: sha512-5EdhGZtnu3V88ces7s53hhfK5KSASnJZv8Lulpc04cWO3REESroJXg73DFsOmgbU2BhwV0E20bu2IDZb3VKW4Q== - } - engines: { node: '>=14.0.0' } + resolution: {integrity: sha512-5EdhGZtnu3V88ces7s53hhfK5KSASnJZv8Lulpc04cWO3REESroJXg73DFsOmgbU2BhwV0E20bu2IDZb3VKW4Q==} + engines: {node: '>=14.0.0'} peerDependencies: rollup: ^1.20.0||^2.0.0||^3.0.0||^4.0.0 peerDependenciesMeta: @@ -1139,241 +772,157 @@ packages: optional: true '@rollup/rollup-android-arm-eabi@4.59.0': - resolution: - { - integrity: sha512-upnNBkA6ZH2VKGcBj9Fyl9IGNPULcjXRlg0LLeaioQWueH30p6IXtJEbKAgvyv+mJaMxSm1l6xwDXYjpEMiLMg== - } + resolution: {integrity: sha512-upnNBkA6ZH2VKGcBj9Fyl9IGNPULcjXRlg0LLeaioQWueH30p6IXtJEbKAgvyv+mJaMxSm1l6xwDXYjpEMiLMg==} cpu: [arm] os: [android] '@rollup/rollup-android-arm64@4.59.0': - resolution: - { - integrity: sha512-hZ+Zxj3SySm4A/DylsDKZAeVg0mvi++0PYVceVyX7hemkw7OreKdCvW2oQ3T1FMZvCaQXqOTHb8qmBShoqk69Q== - } + resolution: {integrity: sha512-hZ+Zxj3SySm4A/DylsDKZAeVg0mvi++0PYVceVyX7hemkw7OreKdCvW2oQ3T1FMZvCaQXqOTHb8qmBShoqk69Q==} cpu: [arm64] os: [android] '@rollup/rollup-darwin-arm64@4.59.0': - resolution: - { - integrity: sha512-W2Psnbh1J8ZJw0xKAd8zdNgF9HRLkdWwwdWqubSVk0pUuQkoHnv7rx4GiF9rT4t5DIZGAsConRE3AxCdJ4m8rg== - } + resolution: {integrity: sha512-W2Psnbh1J8ZJw0xKAd8zdNgF9HRLkdWwwdWqubSVk0pUuQkoHnv7rx4GiF9rT4t5DIZGAsConRE3AxCdJ4m8rg==} cpu: [arm64] os: [darwin] '@rollup/rollup-darwin-x64@4.59.0': - resolution: - { - integrity: sha512-ZW2KkwlS4lwTv7ZVsYDiARfFCnSGhzYPdiOU4IM2fDbL+QGlyAbjgSFuqNRbSthybLbIJ915UtZBtmuLrQAT/w== - } + resolution: {integrity: sha512-ZW2KkwlS4lwTv7ZVsYDiARfFCnSGhzYPdiOU4IM2fDbL+QGlyAbjgSFuqNRbSthybLbIJ915UtZBtmuLrQAT/w==} cpu: [x64] os: [darwin] '@rollup/rollup-freebsd-arm64@4.59.0': - resolution: - { - integrity: sha512-EsKaJ5ytAu9jI3lonzn3BgG8iRBjV4LxZexygcQbpiU0wU0ATxhNVEpXKfUa0pS05gTcSDMKpn3Sx+QB9RlTTA== - } + resolution: {integrity: sha512-EsKaJ5ytAu9jI3lonzn3BgG8iRBjV4LxZexygcQbpiU0wU0ATxhNVEpXKfUa0pS05gTcSDMKpn3Sx+QB9RlTTA==} cpu: [arm64] os: [freebsd] '@rollup/rollup-freebsd-x64@4.59.0': - resolution: - { - integrity: sha512-d3DuZi2KzTMjImrxoHIAODUZYoUUMsuUiY4SRRcJy6NJoZ6iIqWnJu9IScV9jXysyGMVuW+KNzZvBLOcpdl3Vg== - } + resolution: {integrity: sha512-d3DuZi2KzTMjImrxoHIAODUZYoUUMsuUiY4SRRcJy6NJoZ6iIqWnJu9IScV9jXysyGMVuW+KNzZvBLOcpdl3Vg==} cpu: [x64] os: [freebsd] '@rollup/rollup-linux-arm-gnueabihf@4.59.0': - resolution: - { - integrity: sha512-t4ONHboXi/3E0rT6OZl1pKbl2Vgxf9vJfWgmUoCEVQVxhW6Cw/c8I6hbbu7DAvgp82RKiH7TpLwxnJeKv2pbsw== - } + resolution: {integrity: sha512-t4ONHboXi/3E0rT6OZl1pKbl2Vgxf9vJfWgmUoCEVQVxhW6Cw/c8I6hbbu7DAvgp82RKiH7TpLwxnJeKv2pbsw==} cpu: [arm] os: [linux] libc: [glibc] '@rollup/rollup-linux-arm-musleabihf@4.59.0': - resolution: - { - integrity: sha512-CikFT7aYPA2ufMD086cVORBYGHffBo4K8MQ4uPS/ZnY54GKj36i196u8U+aDVT2LX4eSMbyHtyOh7D7Zvk2VvA== - } + resolution: {integrity: sha512-CikFT7aYPA2ufMD086cVORBYGHffBo4K8MQ4uPS/ZnY54GKj36i196u8U+aDVT2LX4eSMbyHtyOh7D7Zvk2VvA==} cpu: [arm] os: [linux] libc: [musl] '@rollup/rollup-linux-arm64-gnu@4.59.0': - resolution: - { - integrity: sha512-jYgUGk5aLd1nUb1CtQ8E+t5JhLc9x5WdBKew9ZgAXg7DBk0ZHErLHdXM24rfX+bKrFe+Xp5YuJo54I5HFjGDAA== - } + resolution: {integrity: sha512-jYgUGk5aLd1nUb1CtQ8E+t5JhLc9x5WdBKew9ZgAXg7DBk0ZHErLHdXM24rfX+bKrFe+Xp5YuJo54I5HFjGDAA==} cpu: [arm64] os: [linux] libc: [glibc] '@rollup/rollup-linux-arm64-musl@4.59.0': - resolution: - { - integrity: sha512-peZRVEdnFWZ5Bh2KeumKG9ty7aCXzzEsHShOZEFiCQlDEepP1dpUl/SrUNXNg13UmZl+gzVDPsiCwnV1uI0RUA== - } + resolution: {integrity: sha512-peZRVEdnFWZ5Bh2KeumKG9ty7aCXzzEsHShOZEFiCQlDEepP1dpUl/SrUNXNg13UmZl+gzVDPsiCwnV1uI0RUA==} cpu: [arm64] os: [linux] libc: [musl] '@rollup/rollup-linux-loong64-gnu@4.59.0': - resolution: - { - integrity: sha512-gbUSW/97f7+r4gHy3Jlup8zDG190AuodsWnNiXErp9mT90iCy9NKKU0Xwx5k8VlRAIV2uU9CsMnEFg/xXaOfXg== - } + resolution: {integrity: sha512-gbUSW/97f7+r4gHy3Jlup8zDG190AuodsWnNiXErp9mT90iCy9NKKU0Xwx5k8VlRAIV2uU9CsMnEFg/xXaOfXg==} cpu: [loong64] os: [linux] libc: [glibc] '@rollup/rollup-linux-loong64-musl@4.59.0': - resolution: - { - integrity: sha512-yTRONe79E+o0FWFijasoTjtzG9EBedFXJMl888NBEDCDV9I2wGbFFfJQQe63OijbFCUZqxpHz1GzpbtSFikJ4Q== - } + resolution: {integrity: sha512-yTRONe79E+o0FWFijasoTjtzG9EBedFXJMl888NBEDCDV9I2wGbFFfJQQe63OijbFCUZqxpHz1GzpbtSFikJ4Q==} cpu: [loong64] os: [linux] libc: [musl] '@rollup/rollup-linux-ppc64-gnu@4.59.0': - resolution: - { - integrity: sha512-sw1o3tfyk12k3OEpRddF68a1unZ5VCN7zoTNtSn2KndUE+ea3m3ROOKRCZxEpmT9nsGnogpFP9x6mnLTCaoLkA== - } + resolution: {integrity: sha512-sw1o3tfyk12k3OEpRddF68a1unZ5VCN7zoTNtSn2KndUE+ea3m3ROOKRCZxEpmT9nsGnogpFP9x6mnLTCaoLkA==} cpu: [ppc64] os: [linux] libc: [glibc] '@rollup/rollup-linux-ppc64-musl@4.59.0': - resolution: - { - integrity: sha512-+2kLtQ4xT3AiIxkzFVFXfsmlZiG5FXYW7ZyIIvGA7Bdeuh9Z0aN4hVyXS/G1E9bTP/vqszNIN/pUKCk/BTHsKA== - } + resolution: {integrity: sha512-+2kLtQ4xT3AiIxkzFVFXfsmlZiG5FXYW7ZyIIvGA7Bdeuh9Z0aN4hVyXS/G1E9bTP/vqszNIN/pUKCk/BTHsKA==} cpu: [ppc64] os: [linux] libc: [musl] '@rollup/rollup-linux-riscv64-gnu@4.59.0': - resolution: - { - integrity: sha512-NDYMpsXYJJaj+I7UdwIuHHNxXZ/b/N2hR15NyH3m2qAtb/hHPA4g4SuuvrdxetTdndfj9b1WOmy73kcPRoERUg== - } + resolution: {integrity: sha512-NDYMpsXYJJaj+I7UdwIuHHNxXZ/b/N2hR15NyH3m2qAtb/hHPA4g4SuuvrdxetTdndfj9b1WOmy73kcPRoERUg==} cpu: [riscv64] os: [linux] libc: [glibc] '@rollup/rollup-linux-riscv64-musl@4.59.0': - resolution: - { - integrity: sha512-nLckB8WOqHIf1bhymk+oHxvM9D3tyPndZH8i8+35p/1YiVoVswPid2yLzgX7ZJP0KQvnkhM4H6QZ5m0LzbyIAg== - } + resolution: {integrity: sha512-nLckB8WOqHIf1bhymk+oHxvM9D3tyPndZH8i8+35p/1YiVoVswPid2yLzgX7ZJP0KQvnkhM4H6QZ5m0LzbyIAg==} cpu: [riscv64] os: [linux] libc: [musl] '@rollup/rollup-linux-s390x-gnu@4.59.0': - resolution: - { - integrity: sha512-oF87Ie3uAIvORFBpwnCvUzdeYUqi2wY6jRFWJAy1qus/udHFYIkplYRW+wo+GRUP4sKzYdmE1Y3+rY5Gc4ZO+w== - } + resolution: {integrity: sha512-oF87Ie3uAIvORFBpwnCvUzdeYUqi2wY6jRFWJAy1qus/udHFYIkplYRW+wo+GRUP4sKzYdmE1Y3+rY5Gc4ZO+w==} cpu: [s390x] os: [linux] libc: [glibc] '@rollup/rollup-linux-x64-gnu@4.59.0': - resolution: - { - integrity: sha512-3AHmtQq/ppNuUspKAlvA8HtLybkDflkMuLK4DPo77DfthRb71V84/c4MlWJXixZz4uruIH4uaa07IqoAkG64fg== - } + resolution: {integrity: sha512-3AHmtQq/ppNuUspKAlvA8HtLybkDflkMuLK4DPo77DfthRb71V84/c4MlWJXixZz4uruIH4uaa07IqoAkG64fg==} cpu: [x64] os: [linux] libc: [glibc] '@rollup/rollup-linux-x64-musl@4.59.0': - resolution: - { - integrity: sha512-2UdiwS/9cTAx7qIUZB/fWtToJwvt0Vbo0zmnYt7ED35KPg13Q0ym1g442THLC7VyI6JfYTP4PiSOWyoMdV2/xg== - } + resolution: {integrity: sha512-2UdiwS/9cTAx7qIUZB/fWtToJwvt0Vbo0zmnYt7ED35KPg13Q0ym1g442THLC7VyI6JfYTP4PiSOWyoMdV2/xg==} cpu: [x64] os: [linux] libc: [musl] '@rollup/rollup-openbsd-x64@4.59.0': - resolution: - { - integrity: sha512-M3bLRAVk6GOwFlPTIxVBSYKUaqfLrn8l0psKinkCFxl4lQvOSz8ZrKDz2gxcBwHFpci0B6rttydI4IpS4IS/jQ== - } + resolution: {integrity: sha512-M3bLRAVk6GOwFlPTIxVBSYKUaqfLrn8l0psKinkCFxl4lQvOSz8ZrKDz2gxcBwHFpci0B6rttydI4IpS4IS/jQ==} cpu: [x64] os: [openbsd] '@rollup/rollup-openharmony-arm64@4.59.0': - resolution: - { - integrity: sha512-tt9KBJqaqp5i5HUZzoafHZX8b5Q2Fe7UjYERADll83O4fGqJ49O1FsL6LpdzVFQcpwvnyd0i+K/VSwu/o/nWlA== - } + resolution: {integrity: sha512-tt9KBJqaqp5i5HUZzoafHZX8b5Q2Fe7UjYERADll83O4fGqJ49O1FsL6LpdzVFQcpwvnyd0i+K/VSwu/o/nWlA==} cpu: [arm64] os: [openharmony] '@rollup/rollup-win32-arm64-msvc@4.59.0': - resolution: - { - integrity: sha512-V5B6mG7OrGTwnxaNUzZTDTjDS7F75PO1ae6MJYdiMu60sq0CqN5CVeVsbhPxalupvTX8gXVSU9gq+Rx1/hvu6A== - } + resolution: {integrity: sha512-V5B6mG7OrGTwnxaNUzZTDTjDS7F75PO1ae6MJYdiMu60sq0CqN5CVeVsbhPxalupvTX8gXVSU9gq+Rx1/hvu6A==} cpu: [arm64] os: [win32] '@rollup/rollup-win32-ia32-msvc@4.59.0': - resolution: - { - integrity: sha512-UKFMHPuM9R0iBegwzKF4y0C4J9u8C6MEJgFuXTBerMk7EJ92GFVFYBfOZaSGLu6COf7FxpQNqhNS4c4icUPqxA== - } + resolution: {integrity: sha512-UKFMHPuM9R0iBegwzKF4y0C4J9u8C6MEJgFuXTBerMk7EJ92GFVFYBfOZaSGLu6COf7FxpQNqhNS4c4icUPqxA==} cpu: [ia32] os: [win32] '@rollup/rollup-win32-x64-gnu@4.59.0': - resolution: - { - integrity: sha512-laBkYlSS1n2L8fSo1thDNGrCTQMmxjYY5G0WFWjFFYZkKPjsMBsgJfGf4TLxXrF6RyhI60L8TMOjBMvXiTcxeA== - } + resolution: {integrity: sha512-laBkYlSS1n2L8fSo1thDNGrCTQMmxjYY5G0WFWjFFYZkKPjsMBsgJfGf4TLxXrF6RyhI60L8TMOjBMvXiTcxeA==} cpu: [x64] os: [win32] '@rollup/rollup-win32-x64-msvc@4.59.0': - resolution: - { - integrity: sha512-2HRCml6OztYXyJXAvdDXPKcawukWY2GpR5/nxKp4iBgiO3wcoEGkAaqctIbZcNB6KlUQBIqt8VYkNSj2397EfA== - } + resolution: {integrity: sha512-2HRCml6OztYXyJXAvdDXPKcawukWY2GpR5/nxKp4iBgiO3wcoEGkAaqctIbZcNB6KlUQBIqt8VYkNSj2397EfA==} cpu: [x64] os: [win32] '@sindresorhus/is@0.7.0': - resolution: - { - integrity: sha512-ONhaKPIufzzrlNbqtWFFd+jlnemX6lJAgq9ZeiZtS7I1PIf/la7CW4m83rTXRnVnsMbW2k56pGYu7AUFJD9Pow== - } - engines: { node: '>=4' } + resolution: {integrity: sha512-ONhaKPIufzzrlNbqtWFFd+jlnemX6lJAgq9ZeiZtS7I1PIf/la7CW4m83rTXRnVnsMbW2k56pGYu7AUFJD9Pow==} + engines: {node: '>=4'} '@table-library/react-table-library@4.1.15': - resolution: - { - integrity: sha512-c/FY88KbPhAqt+eFy+TnLsFVn9ntAdQrosZ08/T6QpoCUrESlaBFbMOeWvNSV1F4KyUo0RccSJAoSVbJYL5QlA== - } + resolution: {integrity: sha512-c/FY88KbPhAqt+eFy+TnLsFVn9ntAdQrosZ08/T6QpoCUrESlaBFbMOeWvNSV1F4KyUo0RccSJAoSVbJYL5QlA==} peerDependencies: '@emotion/react': '>= 11' react: '>=16.8.0' react-dom: '>=16.8.0' '@trivago/prettier-plugin-sort-imports@6.0.2': - resolution: - { - integrity: sha512-3DgfkukFyC/sE/VuYjaUUWoFfuVjPK55vOFDsxD56XXynFMCZDYFogH2l/hDfOsQAm1myoU/1xByJ3tWqtulXA== - } - engines: { node: '>= 20' } + resolution: {integrity: sha512-3DgfkukFyC/sE/VuYjaUUWoFfuVjPK55vOFDsxD56XXynFMCZDYFogH2l/hDfOsQAm1myoU/1xByJ3tWqtulXA==} + engines: {node: '>= 20'} peerDependencies: '@vue/compiler-sfc': 3.x prettier: 2.x - 3.x @@ -1391,788 +940,443 @@ packages: optional: true '@tybys/wasm-util@0.10.1': - resolution: - { - integrity: sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg== - } + resolution: {integrity: sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==} '@types/esrecurse@4.3.1': - resolution: - { - integrity: sha512-xJBAbDifo5hpffDBuHl0Y8ywswbiAp/Wi7Y/GtAgSlZyIABppyurxVueOPE8LUQOxdlgi6Zqce7uoEpqNTeiUw== - } + resolution: {integrity: sha512-xJBAbDifo5hpffDBuHl0Y8ywswbiAp/Wi7Y/GtAgSlZyIABppyurxVueOPE8LUQOxdlgi6Zqce7uoEpqNTeiUw==} '@types/estree@1.0.8': - resolution: - { - integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w== - } + resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} '@types/glob@7.2.0': - resolution: - { - integrity: sha512-ZUxbzKl0IfJILTS6t7ip5fQQM/J3TJYubDm3nMbgubNNYS62eXeUpoLUC8/7fJNiFYHTrGPQn7hspDUzIHX3UA== - } + resolution: {integrity: sha512-ZUxbzKl0IfJILTS6t7ip5fQQM/J3TJYubDm3nMbgubNNYS62eXeUpoLUC8/7fJNiFYHTrGPQn7hspDUzIHX3UA==} '@types/imagemin-gifsicle@7.0.4': - resolution: - { - integrity: sha512-ZghMBd/Jgqg5utTJNPmvf6DkuHzMhscJ8vgf/7MUGCpO+G+cLrhYltL+5d+h3A1B4W73S2SrmJZ1jS5LACpX+A== - } + resolution: {integrity: sha512-ZghMBd/Jgqg5utTJNPmvf6DkuHzMhscJ8vgf/7MUGCpO+G+cLrhYltL+5d+h3A1B4W73S2SrmJZ1jS5LACpX+A==} '@types/imagemin-jpegtran@5.0.4': - resolution: - { - integrity: sha512-PSMxOeJa8q94Y+qx8Yriw+qj1+vH5xWpvar63o6SGO0Xi5RlKuwHHfJmN2GRUngPrlhe394jOUmpVq8jQlVmFA== - } + resolution: {integrity: sha512-PSMxOeJa8q94Y+qx8Yriw+qj1+vH5xWpvar63o6SGO0Xi5RlKuwHHfJmN2GRUngPrlhe394jOUmpVq8jQlVmFA==} '@types/imagemin-mozjpeg@8.0.4': - resolution: - { - integrity: sha512-ZCAxV8SYJB8ehwHpnbRpHjg5Wc4HcyuAMiDhXbkgC7gujDoOTyHO3dhDkUtZ1oK1DLBRZapqG9etdLVhUml7yQ== - } + resolution: {integrity: sha512-ZCAxV8SYJB8ehwHpnbRpHjg5Wc4HcyuAMiDhXbkgC7gujDoOTyHO3dhDkUtZ1oK1DLBRZapqG9etdLVhUml7yQ==} '@types/imagemin-optipng@5.2.4': - resolution: - { - integrity: sha512-mvKnDMC8eCYZetAQudjs1DbgpR84WhsTx1wgvdiXnpuUEti3oJ+MaMYBRWPY0JlQ4+y4TXKOfa7+LOuT8daegQ== - } + resolution: {integrity: sha512-mvKnDMC8eCYZetAQudjs1DbgpR84WhsTx1wgvdiXnpuUEti3oJ+MaMYBRWPY0JlQ4+y4TXKOfa7+LOuT8daegQ==} '@types/imagemin-svgo@10.0.5': - resolution: - { - integrity: sha512-9U2Rf7vWBHeqJvzmWNP3vYAKqR0208QqQ9Mkrq9OLIL5AeoF/dRVRou6iUYCufBSim57BpBpCJhZLrTgfS3k1g== - } + resolution: {integrity: sha512-9U2Rf7vWBHeqJvzmWNP3vYAKqR0208QqQ9Mkrq9OLIL5AeoF/dRVRou6iUYCufBSim57BpBpCJhZLrTgfS3k1g==} '@types/imagemin-webp@7.0.3': - resolution: - { - integrity: sha512-C2/EMohS4bzsvY5VJvdzHFdcfmnZoui54DmM/9bFtK57/CgGmKkc+p6n49euPGmMFDDvwm4yVl60nwxcZOmH5A== - } + resolution: {integrity: sha512-C2/EMohS4bzsvY5VJvdzHFdcfmnZoui54DmM/9bFtK57/CgGmKkc+p6n49euPGmMFDDvwm4yVl60nwxcZOmH5A==} '@types/imagemin@7.0.1': - resolution: - { - integrity: sha512-xEn5+M3lDBtI3JxLy6eU3ksoVurygnlG7OYhTqJfGGP4PcvYnfn+IABCmMve7ziM/SneHDm5xgJFKC8hCYPicw== - } + resolution: {integrity: sha512-xEn5+M3lDBtI3JxLy6eU3ksoVurygnlG7OYhTqJfGGP4PcvYnfn+IABCmMve7ziM/SneHDm5xgJFKC8hCYPicw==} '@types/json-schema@7.0.15': - resolution: - { - integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA== - } + resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} '@types/keyv@3.1.4': - resolution: - { - integrity: sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg== - } + resolution: {integrity: sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg==} '@types/minimatch@6.0.0': - resolution: - { - integrity: sha512-zmPitbQ8+6zNutpwgcQuLcsEpn/Cj54Kbn7L5pX0Os5kdWplB7xPgEh/g+SWOB/qmows2gpuCaPyduq8ZZRnxA== - } + resolution: {integrity: sha512-zmPitbQ8+6zNutpwgcQuLcsEpn/Cj54Kbn7L5pX0Os5kdWplB7xPgEh/g+SWOB/qmows2gpuCaPyduq8ZZRnxA==} deprecated: This is a stub types definition. minimatch provides its own type definitions, so you do not need this installed. '@types/node@25.6.0': - resolution: - { - integrity: sha512-+qIYRKdNYJwY3vRCZMdJbPLJAtGjQBudzZzdzwQYkEPQd+PJGixUL5QfvCLDaULoLv+RhT3LDkwEfKaAkgSmNQ== - } + resolution: {integrity: sha512-+qIYRKdNYJwY3vRCZMdJbPLJAtGjQBudzZzdzwQYkEPQd+PJGixUL5QfvCLDaULoLv+RhT3LDkwEfKaAkgSmNQ==} '@types/parse-json@4.0.2': - resolution: - { - integrity: sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw== - } + resolution: {integrity: sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==} '@types/prop-types@15.7.15': - resolution: - { - integrity: sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw== - } + resolution: {integrity: sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==} '@types/react-dom@19.2.3': - resolution: - { - integrity: sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ== - } + resolution: {integrity: sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==} peerDependencies: '@types/react': ^19.2.0 '@types/react-transition-group@4.4.12': - resolution: - { - integrity: sha512-8TV6R3h2j7a91c+1DXdJi3Syo69zzIZbz7Lg5tORM5LEJG7X/E6a1V3drRyBRZq7/utz7A+c4OgYLiLcYGHG6w== - } + resolution: {integrity: sha512-8TV6R3h2j7a91c+1DXdJi3Syo69zzIZbz7Lg5tORM5LEJG7X/E6a1V3drRyBRZq7/utz7A+c4OgYLiLcYGHG6w==} peerDependencies: '@types/react': '*' '@types/react@19.2.14': - resolution: - { - integrity: sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w== - } + resolution: {integrity: sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==} '@types/responselike@1.0.3': - resolution: - { - integrity: sha512-H/+L+UkTV33uf49PH5pCAUBVPNj2nDBXTN+qS1dOwyyg24l3CcicicCA7ca+HMvJBZcFgl5r8e+RR6elsb4Lyw== - } + resolution: {integrity: sha512-H/+L+UkTV33uf49PH5pCAUBVPNj2nDBXTN+qS1dOwyyg24l3CcicicCA7ca+HMvJBZcFgl5r8e+RR6elsb4Lyw==} '@types/svgo@2.6.4': - resolution: - { - integrity: sha512-l4cmyPEckf8moNYHdJ+4wkHvFxjyW6ulm9l4YGaOxeyBWPhBOT0gvni1InpFPdzx1dKf/2s62qGITwxNWnPQng== - } + resolution: {integrity: sha512-l4cmyPEckf8moNYHdJ+4wkHvFxjyW6ulm9l4YGaOxeyBWPhBOT0gvni1InpFPdzx1dKf/2s62qGITwxNWnPQng==} '@typescript-eslint/eslint-plugin@8.59.1': - resolution: - { - integrity: sha512-BOziFIfE+6osHO9FoJG4zjoHUcvI7fTNBSpdAwrNH0/TLvzjsk2oo8XSSOT2HhqUyhZPfHv4UOffoJ9oEEQ7Ag== - } - engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + resolution: {integrity: sha512-BOziFIfE+6osHO9FoJG4zjoHUcvI7fTNBSpdAwrNH0/TLvzjsk2oo8XSSOT2HhqUyhZPfHv4UOffoJ9oEEQ7Ag==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: '@typescript-eslint/parser': ^8.59.1 eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 typescript: '>=4.8.4 <6.1.0' '@typescript-eslint/parser@8.59.1': - resolution: - { - integrity: sha512-HDQH9O/47Dxi1ceDhBXdaldtf/WV9yRYMjbjCuNk3qnaTD564qwv61Y7+gTxwxRKzSrgO5uhtw584igXVuuZkA== - } - engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + resolution: {integrity: sha512-HDQH9O/47Dxi1ceDhBXdaldtf/WV9yRYMjbjCuNk3qnaTD564qwv61Y7+gTxwxRKzSrgO5uhtw584igXVuuZkA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 typescript: '>=4.8.4 <6.1.0' '@typescript-eslint/project-service@8.59.1': - resolution: - { - integrity: sha512-+MuHQlHiEr00Of/IQbE/MmEoi44znZHbR/Pz7Opq4HryUOlRi+/44dro9Ycy8Fyo+/024IWtw8m4JUMCGTYxDg== - } - engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + resolution: {integrity: sha512-+MuHQlHiEr00Of/IQbE/MmEoi44znZHbR/Pz7Opq4HryUOlRi+/44dro9Ycy8Fyo+/024IWtw8m4JUMCGTYxDg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <6.1.0' '@typescript-eslint/scope-manager@8.59.1': - resolution: - { - integrity: sha512-LwuHQI4pDOYVKvmH2dkaJo6YZCSgouVgnS/z7yBPKBMvgtBvyLqiLy9Z6b7+m/TRcX1NFYUqZetI5Y+aT4GEfg== - } - engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + resolution: {integrity: sha512-LwuHQI4pDOYVKvmH2dkaJo6YZCSgouVgnS/z7yBPKBMvgtBvyLqiLy9Z6b7+m/TRcX1NFYUqZetI5Y+aT4GEfg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} '@typescript-eslint/tsconfig-utils@8.59.1': - resolution: - { - integrity: sha512-/0nEyPbX7gRsk0Uwfe4ALwwgxuA66d/l2mhRDNlAvaj4U3juhUtJNq0DsY8M2AYwwb9rEq2hrC3IcIcEt++iJA== - } - engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + resolution: {integrity: sha512-/0nEyPbX7gRsk0Uwfe4ALwwgxuA66d/l2mhRDNlAvaj4U3juhUtJNq0DsY8M2AYwwb9rEq2hrC3IcIcEt++iJA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <6.1.0' '@typescript-eslint/type-utils@8.59.1': - resolution: - { - integrity: sha512-klWPBR2ciQHS3f++ug/mVnWKPjBUo7icEL3FAO1lhAR1Z1i5NQYZ1EannMSRYcq5qCv5wNALlXr6fksRHyYl7w== - } - engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + resolution: {integrity: sha512-klWPBR2ciQHS3f++ug/mVnWKPjBUo7icEL3FAO1lhAR1Z1i5NQYZ1EannMSRYcq5qCv5wNALlXr6fksRHyYl7w==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 typescript: '>=4.8.4 <6.1.0' '@typescript-eslint/types@8.59.1': - resolution: - { - integrity: sha512-ZDCjgccSdYPw5Bxh+my4Z0lJU96ZDN7jbBzvmEn0FZx3RtU1C7VWl6NbDx94bwY3V5YsgwRzJPOgeY2Q/nLG8A== - } - engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + resolution: {integrity: sha512-ZDCjgccSdYPw5Bxh+my4Z0lJU96ZDN7jbBzvmEn0FZx3RtU1C7VWl6NbDx94bwY3V5YsgwRzJPOgeY2Q/nLG8A==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} '@typescript-eslint/typescript-estree@8.59.1': - resolution: - { - integrity: sha512-OUd+vJS05sSkOip+BkZ/2NS8RMxrAAJemsC6vU3kmfLyeaJT0TftHkV9mcx2107MmsBVXXexhVu4F0TZXyMl4g== - } - engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + resolution: {integrity: sha512-OUd+vJS05sSkOip+BkZ/2NS8RMxrAAJemsC6vU3kmfLyeaJT0TftHkV9mcx2107MmsBVXXexhVu4F0TZXyMl4g==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <6.1.0' '@typescript-eslint/utils@8.59.1': - resolution: - { - integrity: sha512-3pIeoXhCeYH9FSCBI8P3iNwJlGuzPlYKkTlen2O9T1DSeeg8UG8jstq6BLk+Mda0qup7mgk4z4XL4OzRaxZ8LA== - } - engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + resolution: {integrity: sha512-3pIeoXhCeYH9FSCBI8P3iNwJlGuzPlYKkTlen2O9T1DSeeg8UG8jstq6BLk+Mda0qup7mgk4z4XL4OzRaxZ8LA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 typescript: '>=4.8.4 <6.1.0' '@typescript-eslint/visitor-keys@8.59.1': - resolution: - { - integrity: sha512-LdDNl6C5iJExcM0Yh0PwAIBb9PrSiCsWamF/JyEZawm3kFDnRoaq3LGE4bpyRao/fWeGKKyw7icx0YxrLFC5Cg== - } - engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + resolution: {integrity: sha512-LdDNl6C5iJExcM0Yh0PwAIBb9PrSiCsWamF/JyEZawm3kFDnRoaq3LGE4bpyRao/fWeGKKyw7icx0YxrLFC5Cg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} acorn-jsx@5.3.2: - resolution: - { - integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ== - } + resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} peerDependencies: acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 acorn@8.16.0: - resolution: - { - integrity: sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw== - } - engines: { node: '>=0.4.0' } + resolution: {integrity: sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==} + engines: {node: '>=0.4.0'} hasBin: true ajv@6.15.0: - resolution: - { - integrity: sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw== - } + resolution: {integrity: sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==} alova@3.5.1: - resolution: - { - integrity: sha512-avrWPyFFWW51YLoy0S3OleNw1BV0GqNI+DSdWHfFbAoKZp80cXCCc7OtjA6OWeyhCOMglUMwo9O8j5huwnzFtQ== - } - engines: { node: '>= 18.0.0' } + resolution: {integrity: sha512-avrWPyFFWW51YLoy0S3OleNw1BV0GqNI+DSdWHfFbAoKZp80cXCCc7OtjA6OWeyhCOMglUMwo9O8j5huwnzFtQ==} + engines: {node: '>= 18.0.0'} ansi-regex@2.1.1: - resolution: - { - integrity: sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA== - } - engines: { node: '>=0.10.0' } + resolution: {integrity: sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==} + engines: {node: '>=0.10.0'} ansi-regex@5.0.1: - resolution: - { - integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== - } - engines: { node: '>=8' } + resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} + engines: {node: '>=8'} ansi-regex@6.2.2: - resolution: - { - integrity: sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg== - } - engines: { node: '>=12' } + resolution: {integrity: sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==} + engines: {node: '>=12'} ansi-styles@2.2.1: - resolution: - { - integrity: sha512-kmCevFghRiWM7HB5zTPULl4r9bVFSWjz62MhqizDGUrq2NWuNMQyuv4tHHoKJHs69M/MF64lEcHdYIocrdWQYA== - } - engines: { node: '>=0.10.0' } + resolution: {integrity: sha512-kmCevFghRiWM7HB5zTPULl4r9bVFSWjz62MhqizDGUrq2NWuNMQyuv4tHHoKJHs69M/MF64lEcHdYIocrdWQYA==} + engines: {node: '>=0.10.0'} ansi-styles@4.3.0: - resolution: - { - integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== - } - engines: { node: '>=8' } + resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} + engines: {node: '>=8'} ansi-styles@6.2.3: - resolution: - { - integrity: sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg== - } - engines: { node: '>=12' } + resolution: {integrity: sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==} + engines: {node: '>=12'} arch@2.2.0: - resolution: - { - integrity: sha512-Of/R0wqp83cgHozfIYLbBMnej79U/SVGOOyuB3VVFv1NRM/PSFMK12x9KVtiYzJqmnU5WR2qp0Z5rHb7sWGnFQ== - } + resolution: {integrity: sha512-Of/R0wqp83cgHozfIYLbBMnej79U/SVGOOyuB3VVFv1NRM/PSFMK12x9KVtiYzJqmnU5WR2qp0Z5rHb7sWGnFQ==} archive-type@4.0.0: - resolution: - { - integrity: sha512-zV4Ky0v1F8dBrdYElwTvQhweQ0P7Kwc1aluqJsYtOBP01jXcWCyW2IEfI1YiqsG+Iy7ZR+o5LF1N+PGECBxHWA== - } - engines: { node: '>=4' } + resolution: {integrity: sha512-zV4Ky0v1F8dBrdYElwTvQhweQ0P7Kwc1aluqJsYtOBP01jXcWCyW2IEfI1YiqsG+Iy7ZR+o5LF1N+PGECBxHWA==} + engines: {node: '>=4'} array-find-index@1.0.2: - resolution: - { - integrity: sha512-M1HQyIXcBGtVywBt8WVdim+lrNaK7VHp99Qt5pSNziXznKHViIBbXWtfRTpEFpF/c4FdfxNAsCCwPp5phBYJtw== - } - engines: { node: '>=0.10.0' } + resolution: {integrity: sha512-M1HQyIXcBGtVywBt8WVdim+lrNaK7VHp99Qt5pSNziXznKHViIBbXWtfRTpEFpF/c4FdfxNAsCCwPp5phBYJtw==} + engines: {node: '>=0.10.0'} array-union@2.1.0: - resolution: - { - integrity: sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw== - } - engines: { node: '>=8' } + resolution: {integrity: sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==} + engines: {node: '>=8'} async-validator@4.2.5: - resolution: - { - integrity: sha512-7HhHjtERjqlNbZtqNqy2rckN/SpOOlmDliet+lP7k+eKZEjPk3DgyeU9lIXLdeLz0uBbbVp+9Qdow9wJWgwwfg== - } + resolution: {integrity: sha512-7HhHjtERjqlNbZtqNqy2rckN/SpOOlmDliet+lP7k+eKZEjPk3DgyeU9lIXLdeLz0uBbbVp+9Qdow9wJWgwwfg==} available-typed-arrays@1.0.7: - resolution: - { - integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ== - } - engines: { node: '>= 0.4' } + resolution: {integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==} + engines: {node: '>= 0.4'} babel-plugin-macros@3.1.0: - resolution: - { - integrity: sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg== - } - engines: { node: '>=10', npm: '>=6' } + resolution: {integrity: sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg==} + engines: {node: '>=10', npm: '>=6'} babel-plugin-transform-hook-names@1.0.2: - resolution: - { - integrity: sha512-5gafyjyyBTTdX/tQQ0hRgu4AhNHG/hqWi0ZZmg2xvs2FgRkJXzDNKBZCyoYqgFkovfDrgM8OoKg8karoUvWeCw== - } + resolution: {integrity: sha512-5gafyjyyBTTdX/tQQ0hRgu4AhNHG/hqWi0ZZmg2xvs2FgRkJXzDNKBZCyoYqgFkovfDrgM8OoKg8karoUvWeCw==} peerDependencies: '@babel/core': ^7.12.10 balanced-match@1.0.2: - resolution: - { - integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== - } + resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} balanced-match@4.0.4: - resolution: - { - integrity: sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA== - } - engines: { node: 18 || 20 || >=22 } + resolution: {integrity: sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==} + engines: {node: 18 || 20 || >=22} base64-js@1.5.1: - resolution: - { - integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== - } + resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} - baseline-browser-mapping@2.10.24: - resolution: - { - integrity: sha512-I2NkZOOrj2XuguvWCK6OVh9GavsNjZjK908Rq3mIBK25+GD8vPX5w2WdxVqnQ7xx3SrZJiCiZFu+/Oz50oSYSA== - } - engines: { node: '>=6.0.0' } + baseline-browser-mapping@2.10.25: + resolution: {integrity: sha512-QO/VHsXCQdnzADMfmkeOPvHdIAkoB7i0/rGjINPJEetLx75hNttVWGQ/jycHUDP9zZ9rupbm60WRxcwViB0MiA==} + engines: {node: '>=6.0.0'} hasBin: true bin-build@3.0.0: - resolution: - { - integrity: sha512-jcUOof71/TNAI2uM5uoUaDq2ePcVBQ3R/qhxAz1rX7UfvduAL/RXD3jXzvn8cVcDJdGVkiR1shal3OH0ImpuhA== - } - engines: { node: '>=4' } + resolution: {integrity: sha512-jcUOof71/TNAI2uM5uoUaDq2ePcVBQ3R/qhxAz1rX7UfvduAL/RXD3jXzvn8cVcDJdGVkiR1shal3OH0ImpuhA==} + engines: {node: '>=4'} bin-check@4.1.0: - resolution: - { - integrity: sha512-b6weQyEUKsDGFlACWSIOfveEnImkJyK/FGW6FAG42loyoquvjdtOIqO6yBFzHyqyVVhNgNkQxxx09SFLK28YnA== - } - engines: { node: '>=4' } + resolution: {integrity: sha512-b6weQyEUKsDGFlACWSIOfveEnImkJyK/FGW6FAG42loyoquvjdtOIqO6yBFzHyqyVVhNgNkQxxx09SFLK28YnA==} + engines: {node: '>=4'} bin-version-check@4.0.0: - resolution: - { - integrity: sha512-sR631OrhC+1f8Cvs8WyVWOA33Y8tgwjETNPyyD/myRBXLkfS/vl74FmH/lFcRl9KY3zwGh7jFhvyk9vV3/3ilQ== - } - engines: { node: '>=6' } + resolution: {integrity: sha512-sR631OrhC+1f8Cvs8WyVWOA33Y8tgwjETNPyyD/myRBXLkfS/vl74FmH/lFcRl9KY3zwGh7jFhvyk9vV3/3ilQ==} + engines: {node: '>=6'} bin-version@3.1.0: - resolution: - { - integrity: sha512-Mkfm4iE1VFt4xd4vH+gx+0/71esbfus2LsnCGe8Pi4mndSPyT+NGES/Eg99jx8/lUGWfu3z2yuB/bt5UB+iVbQ== - } - engines: { node: '>=6' } + resolution: {integrity: sha512-Mkfm4iE1VFt4xd4vH+gx+0/71esbfus2LsnCGe8Pi4mndSPyT+NGES/Eg99jx8/lUGWfu3z2yuB/bt5UB+iVbQ==} + engines: {node: '>=6'} bin-wrapper@4.1.0: - resolution: - { - integrity: sha512-hfRmo7hWIXPkbpi0ZltboCMVrU+0ClXR/JgbCKKjlDjQf6igXa7OwdqNcFWQZPZTgiY7ZpzE3+LjjkLiTN2T7Q== - } - engines: { node: '>=6' } + resolution: {integrity: sha512-hfRmo7hWIXPkbpi0ZltboCMVrU+0ClXR/JgbCKKjlDjQf6igXa7OwdqNcFWQZPZTgiY7ZpzE3+LjjkLiTN2T7Q==} + engines: {node: '>=6'} bl@1.2.3: - resolution: - { - integrity: sha512-pvcNpa0UU69UT341rO6AYy4FVAIkUHuZXRIWbq+zHnsVcRzDDjIAhGuuYoi0d//cwIwtt4pkpKycWEfjdV+vww== - } + resolution: {integrity: sha512-pvcNpa0UU69UT341rO6AYy4FVAIkUHuZXRIWbq+zHnsVcRzDDjIAhGuuYoi0d//cwIwtt4pkpKycWEfjdV+vww==} boolbase@1.0.0: - resolution: - { - integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww== - } + resolution: {integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==} brace-expansion@1.1.14: - resolution: - { - integrity: sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g== - } + resolution: {integrity: sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==} brace-expansion@2.1.0: - resolution: - { - integrity: sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w== - } + resolution: {integrity: sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w==} brace-expansion@5.0.5: - resolution: - { - integrity: sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ== - } - engines: { node: 18 || 20 || >=22 } + resolution: {integrity: sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==} + engines: {node: 18 || 20 || >=22} braces@3.0.3: - resolution: - { - integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA== - } - engines: { node: '>=8' } + resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} + engines: {node: '>=8'} browserslist@4.28.2: - resolution: - { - integrity: sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg== - } - engines: { node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7 } + resolution: {integrity: sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg==} + engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} hasBin: true buffer-alloc-unsafe@1.1.0: - resolution: - { - integrity: sha512-TEM2iMIEQdJ2yjPJoSIsldnleVaAk1oW3DBVUykyOLsEsFmEc9kn+SFFPz+gl54KQNxlDnAwCXosOS9Okx2xAg== - } + resolution: {integrity: sha512-TEM2iMIEQdJ2yjPJoSIsldnleVaAk1oW3DBVUykyOLsEsFmEc9kn+SFFPz+gl54KQNxlDnAwCXosOS9Okx2xAg==} buffer-alloc@1.2.0: - resolution: - { - integrity: sha512-CFsHQgjtW1UChdXgbyJGtnm+O/uLQeZdtbDo8mfUgYXCHSM1wgrVxXm6bSyrUuErEb+4sYVGCzASBRot7zyrow== - } + resolution: {integrity: sha512-CFsHQgjtW1UChdXgbyJGtnm+O/uLQeZdtbDo8mfUgYXCHSM1wgrVxXm6bSyrUuErEb+4sYVGCzASBRot7zyrow==} buffer-crc32@0.2.13: - resolution: - { - integrity: sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ== - } + resolution: {integrity: sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==} buffer-fill@1.0.0: - resolution: - { - integrity: sha512-T7zexNBwiiaCOGDg9xNX9PBmjrubblRkENuptryuI64URkXDFum9il/JGL8Lm8wYfAXpredVXXZz7eMHilimiQ== - } + resolution: {integrity: sha512-T7zexNBwiiaCOGDg9xNX9PBmjrubblRkENuptryuI64URkXDFum9il/JGL8Lm8wYfAXpredVXXZz7eMHilimiQ==} buffer-from@1.1.2: - resolution: - { - integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ== - } + resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} buffer@5.7.1: - resolution: - { - integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ== - } + resolution: {integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==} bundle-name@4.1.0: - resolution: - { - integrity: sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q== - } - engines: { node: '>=18' } + resolution: {integrity: sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==} + engines: {node: '>=18'} cacheable-request@2.1.4: - resolution: - { - integrity: sha512-vag0O2LKZ/najSoUwDbVlnlCFvhBE/7mGTY2B5FgCBDcRD+oVV1HYTOwM6JZfMg/hIcM6IwnTZ1uQQL5/X3xIQ== - } + resolution: {integrity: sha512-vag0O2LKZ/najSoUwDbVlnlCFvhBE/7mGTY2B5FgCBDcRD+oVV1HYTOwM6JZfMg/hIcM6IwnTZ1uQQL5/X3xIQ==} call-bind-apply-helpers@1.0.2: - resolution: - { - integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ== - } - engines: { node: '>= 0.4' } + resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} + engines: {node: '>= 0.4'} call-bind@1.0.9: - resolution: - { - integrity: sha512-a/hy+pNsFUTR+Iz8TCJvXudKVLAnz/DyeSUo10I5yvFDQJBFU2s9uqQpoSrJlroHUKoKqzg+epxyP9lqFdzfBQ== - } - engines: { node: '>= 0.4' } + resolution: {integrity: sha512-a/hy+pNsFUTR+Iz8TCJvXudKVLAnz/DyeSUo10I5yvFDQJBFU2s9uqQpoSrJlroHUKoKqzg+epxyP9lqFdzfBQ==} + engines: {node: '>= 0.4'} call-bound@1.0.4: - resolution: - { - integrity: sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg== - } - engines: { node: '>= 0.4' } + resolution: {integrity: sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==} + engines: {node: '>= 0.4'} callsites@3.1.0: - resolution: - { - integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ== - } - engines: { node: '>=6' } + resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} + engines: {node: '>=6'} camelcase-keys@2.1.0: - resolution: - { - integrity: sha512-bA/Z/DERHKqoEOrp+qeGKw1QlvEQkGZSc0XaY6VnTxZr+Kv1G5zFwttpjv8qxZ/sBPT4nthwZaAcsAZTJlSKXQ== - } - engines: { node: '>=0.10.0' } + resolution: {integrity: sha512-bA/Z/DERHKqoEOrp+qeGKw1QlvEQkGZSc0XaY6VnTxZr+Kv1G5zFwttpjv8qxZ/sBPT4nthwZaAcsAZTJlSKXQ==} + engines: {node: '>=0.10.0'} camelcase@2.1.1: - resolution: - { - integrity: sha512-DLIsRzJVBQu72meAKPkWQOLcujdXT32hwdfnkI1frSiSRMK1MofjKHf+MEx0SB6fjEFXL8fBDv1dKymBlOp4Qw== - } - engines: { node: '>=0.10.0' } + resolution: {integrity: sha512-DLIsRzJVBQu72meAKPkWQOLcujdXT32hwdfnkI1frSiSRMK1MofjKHf+MEx0SB6fjEFXL8fBDv1dKymBlOp4Qw==} + engines: {node: '>=0.10.0'} caniuse-lite@1.0.30001791: - resolution: - { - integrity: sha512-yk0l/YSrOnFZk3UROpDLQD9+kC1l4meK/wed583AXrzoarMGJcbRi2Q4RaUYbKxYAsZ8sWmaSa/DsLmdBeI1vQ== - } + resolution: {integrity: sha512-yk0l/YSrOnFZk3UROpDLQD9+kC1l4meK/wed583AXrzoarMGJcbRi2Q4RaUYbKxYAsZ8sWmaSa/DsLmdBeI1vQ==} caw@2.0.1: - resolution: - { - integrity: sha512-Cg8/ZSBEa8ZVY9HspcGUYaK63d/bN7rqS3CYCzEGUxuYv6UlmcjzDUz2fCFFHyTvUW5Pk0I+3hkA3iXlIj6guA== - } - engines: { node: '>=4' } + resolution: {integrity: sha512-Cg8/ZSBEa8ZVY9HspcGUYaK63d/bN7rqS3CYCzEGUxuYv6UlmcjzDUz2fCFFHyTvUW5Pk0I+3hkA3iXlIj6guA==} + engines: {node: '>=4'} chalk@1.1.3: - resolution: - { - integrity: sha512-U3lRVLMSlsCfjqYPbLyVv11M9CPW4I728d6TCKMAOJueEeB9/8o+eSsMnxPJD+Q+K909sdESg7C+tIkoH6on1A== - } - engines: { node: '>=0.10.0' } + resolution: {integrity: sha512-U3lRVLMSlsCfjqYPbLyVv11M9CPW4I728d6TCKMAOJueEeB9/8o+eSsMnxPJD+Q+K909sdESg7C+tIkoH6on1A==} + engines: {node: '>=0.10.0'} chalk@4.1.2: - resolution: - { - integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== - } - engines: { node: '>=10' } + resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} + engines: {node: '>=10'} cliui@8.0.1: - resolution: - { - integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ== - } - engines: { node: '>=12' } + resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==} + engines: {node: '>=12'} cliui@9.0.1: - resolution: - { - integrity: sha512-k7ndgKhwoQveBL+/1tqGJYNz097I7WOvwbmmU2AR5+magtbjPWQTS1C5vzGkBC8Ym8UWRzfKUzUUqFLypY4Q+w== - } - engines: { node: '>=20' } + resolution: {integrity: sha512-k7ndgKhwoQveBL+/1tqGJYNz097I7WOvwbmmU2AR5+magtbjPWQTS1C5vzGkBC8Ym8UWRzfKUzUUqFLypY4Q+w==} + engines: {node: '>=20'} clone-response@1.0.2: - resolution: - { - integrity: sha512-yjLXh88P599UOyPTFX0POsd7WxnbsVsGohcwzHOLspIhhpalPw1BcqED8NblyZLKcGrL8dTgMlcaZxV2jAD41Q== - } + resolution: {integrity: sha512-yjLXh88P599UOyPTFX0POsd7WxnbsVsGohcwzHOLspIhhpalPw1BcqED8NblyZLKcGrL8dTgMlcaZxV2jAD41Q==} clsx@1.1.1: - resolution: - { - integrity: sha512-6/bPho624p3S2pMyvP5kKBPXnI3ufHLObBFCfgx+LkeR5lg2XYy2hqZqUf45ypD8COn2bhgGJSUE+l5dhNBieA== - } - engines: { node: '>=6' } + resolution: {integrity: sha512-6/bPho624p3S2pMyvP5kKBPXnI3ufHLObBFCfgx+LkeR5lg2XYy2hqZqUf45ypD8COn2bhgGJSUE+l5dhNBieA==} + engines: {node: '>=6'} clsx@2.1.1: - resolution: - { - integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA== - } - engines: { node: '>=6' } + resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==} + engines: {node: '>=6'} color-convert@2.0.1: - resolution: - { - integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== - } - engines: { node: '>=7.0.0' } + resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} + engines: {node: '>=7.0.0'} color-name@1.1.4: - resolution: - { - integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== - } + resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} commander@2.20.3: - resolution: - { - integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ== - } + resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==} commander@7.2.0: - resolution: - { - integrity: sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw== - } - engines: { node: '>= 10' } + resolution: {integrity: sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==} + engines: {node: '>= 10'} concat-map@0.0.1: - resolution: - { - integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg== - } + resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} concurrently@9.2.1: - resolution: - { - integrity: sha512-fsfrO0MxV64Znoy8/l1vVIjjHa29SZyyqPgQBwhiDcaW8wJc2W3XWVOGx4M3oJBnv/zdUZIIp1gDeS98GzP8Ng== - } - engines: { node: '>=18' } + resolution: {integrity: sha512-fsfrO0MxV64Znoy8/l1vVIjjHa29SZyyqPgQBwhiDcaW8wJc2W3XWVOGx4M3oJBnv/zdUZIIp1gDeS98GzP8Ng==} + engines: {node: '>=18'} hasBin: true config-chain@1.1.13: - resolution: - { - integrity: sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ== - } + resolution: {integrity: sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ==} console-stream@0.1.1: - resolution: - { - integrity: sha512-QC/8l9e6ofi6nqZ5PawlDgzmMw3OxIXtvolBzap/F4UDBJlDaZRSNbL/lb41C29FcbSJncBFlJFj2WJoNyZRfQ== - } + resolution: {integrity: sha512-QC/8l9e6ofi6nqZ5PawlDgzmMw3OxIXtvolBzap/F4UDBJlDaZRSNbL/lb41C29FcbSJncBFlJFj2WJoNyZRfQ==} content-disposition@0.5.4: - resolution: - { - integrity: sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ== - } - engines: { node: '>= 0.6' } + resolution: {integrity: sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==} + engines: {node: '>= 0.6'} convert-source-map@1.9.0: - resolution: - { - integrity: sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A== - } + resolution: {integrity: sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==} convert-source-map@2.0.0: - resolution: - { - integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg== - } + resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} cookie@1.1.1: - resolution: - { - integrity: sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ== - } - engines: { node: '>=18' } + resolution: {integrity: sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==} + engines: {node: '>=18'} core-util-is@1.0.3: - resolution: - { - integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ== - } + resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==} cosmiconfig@7.1.0: - resolution: - { - integrity: sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA== - } - engines: { node: '>=10' } + resolution: {integrity: sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==} + engines: {node: '>=10'} cross-spawn@5.1.0: - resolution: - { - integrity: sha512-pTgQJ5KC0d2hcY8eyL1IzlBPYjTkyH72XRZPnLyKus2mBfNjQs3klqbJU2VILqZryAZUt9JOb3h/mWMy23/f5A== - } + resolution: {integrity: sha512-pTgQJ5KC0d2hcY8eyL1IzlBPYjTkyH72XRZPnLyKus2mBfNjQs3klqbJU2VILqZryAZUt9JOb3h/mWMy23/f5A==} cross-spawn@6.0.6: - resolution: - { - integrity: sha512-VqCUuhcd1iB+dsv8gxPttb5iZh/D0iubSP21g36KXdEuf6I5JiioesUVjpCdHV9MZRUfVFlvwtIUyPfxo5trtw== - } - engines: { node: '>=4.8' } + resolution: {integrity: sha512-VqCUuhcd1iB+dsv8gxPttb5iZh/D0iubSP21g36KXdEuf6I5JiioesUVjpCdHV9MZRUfVFlvwtIUyPfxo5trtw==} + engines: {node: '>=4.8'} cross-spawn@7.0.6: - resolution: - { - integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA== - } - engines: { node: '>= 8' } + resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} + engines: {node: '>= 8'} css-select@4.3.0: - resolution: - { - integrity: sha512-wPpOYtnsVontu2mODhA19JrqWxNsfdatRKd64kmpRbQgh1KtItko5sTnEpPdpSaJszTOhEMlF/RPz28qj4HqhQ== - } + resolution: {integrity: sha512-wPpOYtnsVontu2mODhA19JrqWxNsfdatRKd64kmpRbQgh1KtItko5sTnEpPdpSaJszTOhEMlF/RPz28qj4HqhQ==} css-select@5.2.2: - resolution: - { - integrity: sha512-TizTzUddG/xYLA3NXodFM0fSbNizXjOKhqiQQwvhlspadZokn1KDy0NZFS0wuEubIYAV5/c1/lAr0TaaFXEXzw== - } + resolution: {integrity: sha512-TizTzUddG/xYLA3NXodFM0fSbNizXjOKhqiQQwvhlspadZokn1KDy0NZFS0wuEubIYAV5/c1/lAr0TaaFXEXzw==} css-tree@1.1.3: - resolution: - { - integrity: sha512-tRpdppF7TRazZrjJ6v3stzv93qxRcSsFmW6cX0Zm2NVKpxE1WV1HblnghVv9TreireHkqI/VDEsfolRF1p6y7Q== - } - engines: { node: '>=8.0.0' } + resolution: {integrity: sha512-tRpdppF7TRazZrjJ6v3stzv93qxRcSsFmW6cX0Zm2NVKpxE1WV1HblnghVv9TreireHkqI/VDEsfolRF1p6y7Q==} + engines: {node: '>=8.0.0'} css-what@6.2.2: - resolution: - { - integrity: sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA== - } - engines: { node: '>= 6' } + resolution: {integrity: sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA==} + engines: {node: '>= 6'} csso@4.2.0: - resolution: - { - integrity: sha512-wvlcdIbf6pwKEk7vHj8/Bkc0B4ylXZruLvOgs9doS5eOsOpuodOV2zJChSpkp+pRpYQLQMeF04nr3Z68Sta9jA== - } - engines: { node: '>=8.0.0' } + resolution: {integrity: sha512-wvlcdIbf6pwKEk7vHj8/Bkc0B4ylXZruLvOgs9doS5eOsOpuodOV2zJChSpkp+pRpYQLQMeF04nr3Z68Sta9jA==} + engines: {node: '>=8.0.0'} csstype@3.2.3: - resolution: - { - integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ== - } + resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==} currently-unhandled@0.4.1: - resolution: - { - integrity: sha512-/fITjgjGU50vjQ4FH6eUoYu+iUoUKIXws2hL15JJpIR+BbTxaXQsMuuyjtNh2WqsSBS5nsaZHFsFecyw5CCAng== - } - engines: { node: '>=0.10.0' } + resolution: {integrity: sha512-/fITjgjGU50vjQ4FH6eUoYu+iUoUKIXws2hL15JJpIR+BbTxaXQsMuuyjtNh2WqsSBS5nsaZHFsFecyw5CCAng==} + engines: {node: '>=0.10.0'} cwebp-bin@6.1.2: - resolution: - { - integrity: sha512-NLEZ/BVAl9g426hwUX/qrQ7b/EfQH7BS1tr+CzPo2EgDQbcdzmUVE+fIfsi64lsL638lWgzTEViMAL4pxV1GOg== - } - engines: { node: '>=10' } + resolution: {integrity: sha512-NLEZ/BVAl9g426hwUX/qrQ7b/EfQH7BS1tr+CzPo2EgDQbcdzmUVE+fIfsi64lsL638lWgzTEViMAL4pxV1GOg==} + engines: {node: '>=10'} hasBin: true debug@4.4.3: - resolution: - { - integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA== - } - engines: { node: '>=6.0' } + resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} + engines: {node: '>=6.0'} peerDependencies: supports-color: '*' peerDependenciesMeta: @@ -2180,503 +1384,302 @@ packages: optional: true decamelize@1.2.0: - resolution: - { - integrity: sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA== - } - engines: { node: '>=0.10.0' } + resolution: {integrity: sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==} + engines: {node: '>=0.10.0'} decode-uri-component@0.2.2: - resolution: - { - integrity: sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ== - } - engines: { node: '>=0.10' } + resolution: {integrity: sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ==} + engines: {node: '>=0.10'} decompress-response@3.3.0: - resolution: - { - integrity: sha512-BzRPQuY1ip+qDonAOz42gRm/pg9F768C+npV/4JOsxRC2sq+Rlk+Q4ZCAsOhnIaMrgarILY+RMUIvMmmX1qAEA== - } - engines: { node: '>=4' } + resolution: {integrity: sha512-BzRPQuY1ip+qDonAOz42gRm/pg9F768C+npV/4JOsxRC2sq+Rlk+Q4ZCAsOhnIaMrgarILY+RMUIvMmmX1qAEA==} + engines: {node: '>=4'} decompress-tar@4.1.1: - resolution: - { - integrity: sha512-JdJMaCrGpB5fESVyxwpCx4Jdj2AagLmv3y58Qy4GE6HMVjWz1FeVQk1Ct4Kye7PftcdOo/7U7UKzYBJgqnGeUQ== - } - engines: { node: '>=4' } + resolution: {integrity: sha512-JdJMaCrGpB5fESVyxwpCx4Jdj2AagLmv3y58Qy4GE6HMVjWz1FeVQk1Ct4Kye7PftcdOo/7U7UKzYBJgqnGeUQ==} + engines: {node: '>=4'} decompress-tarbz2@4.1.1: - resolution: - { - integrity: sha512-s88xLzf1r81ICXLAVQVzaN6ZmX4A6U4z2nMbOwobxkLoIIfjVMBg7TeguTUXkKeXni795B6y5rnvDw7rxhAq9A== - } - engines: { node: '>=4' } + resolution: {integrity: sha512-s88xLzf1r81ICXLAVQVzaN6ZmX4A6U4z2nMbOwobxkLoIIfjVMBg7TeguTUXkKeXni795B6y5rnvDw7rxhAq9A==} + engines: {node: '>=4'} decompress-targz@4.1.1: - resolution: - { - integrity: sha512-4z81Znfr6chWnRDNfFNqLwPvm4db3WuZkqV+UgXQzSngG3CEKdBkw5jrv3axjjL96glyiiKjsxJG3X6WBZwX3w== - } - engines: { node: '>=4' } + resolution: {integrity: sha512-4z81Znfr6chWnRDNfFNqLwPvm4db3WuZkqV+UgXQzSngG3CEKdBkw5jrv3axjjL96glyiiKjsxJG3X6WBZwX3w==} + engines: {node: '>=4'} decompress-unzip@4.0.1: - resolution: - { - integrity: sha512-1fqeluvxgnn86MOh66u8FjbtJpAFv5wgCT9Iw8rcBqQcCo5tO8eiJw7NNTrvt9n4CRBVq7CstiS922oPgyGLrw== - } - engines: { node: '>=4' } + resolution: {integrity: sha512-1fqeluvxgnn86MOh66u8FjbtJpAFv5wgCT9Iw8rcBqQcCo5tO8eiJw7NNTrvt9n4CRBVq7CstiS922oPgyGLrw==} + engines: {node: '>=4'} decompress@4.2.1: - resolution: - { - integrity: sha512-e48kc2IjU+2Zw8cTb6VZcJQ3lgVbS4uuB1TfCHbiZIP/haNXm+SVyhu+87jts5/3ROpd82GSVCoNs/z8l4ZOaQ== - } - engines: { node: '>=4' } + resolution: {integrity: sha512-e48kc2IjU+2Zw8cTb6VZcJQ3lgVbS4uuB1TfCHbiZIP/haNXm+SVyhu+87jts5/3ROpd82GSVCoNs/z8l4ZOaQ==} + engines: {node: '>=4'} deep-is@0.1.4: - resolution: - { - integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ== - } + resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} default-browser-id@5.0.1: - resolution: - { - integrity: sha512-x1VCxdX4t+8wVfd1so/9w+vQ4vx7lKd2Qp5tDRutErwmR85OgmfX7RlLRMWafRMY7hbEiXIbudNrjOAPa/hL8Q== - } - engines: { node: '>=18' } + resolution: {integrity: sha512-x1VCxdX4t+8wVfd1so/9w+vQ4vx7lKd2Qp5tDRutErwmR85OgmfX7RlLRMWafRMY7hbEiXIbudNrjOAPa/hL8Q==} + engines: {node: '>=18'} default-browser@5.5.0: - resolution: - { - integrity: sha512-H9LMLr5zwIbSxrmvikGuI/5KGhZ8E2zH3stkMgM5LpOWDutGM2JZaj460Udnf1a+946zc7YBgrqEWwbk7zHvGw== - } - engines: { node: '>=18' } + resolution: {integrity: sha512-H9LMLr5zwIbSxrmvikGuI/5KGhZ8E2zH3stkMgM5LpOWDutGM2JZaj460Udnf1a+946zc7YBgrqEWwbk7zHvGw==} + engines: {node: '>=18'} define-data-property@1.1.4: - resolution: - { - integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A== - } - engines: { node: '>= 0.4' } + resolution: {integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==} + engines: {node: '>= 0.4'} define-lazy-prop@3.0.0: - resolution: - { - integrity: sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg== - } - engines: { node: '>=12' } + resolution: {integrity: sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==} + engines: {node: '>=12'} detect-libc@2.1.2: - resolution: - { - integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ== - } - engines: { node: '>=8' } + resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} + engines: {node: '>=8'} dir-glob@3.0.1: - resolution: - { - integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA== - } - engines: { node: '>=8' } + resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} + engines: {node: '>=8'} dom-helpers@5.2.1: - resolution: - { - integrity: sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA== - } + resolution: {integrity: sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==} dom-serializer@1.4.1: - resolution: - { - integrity: sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag== - } + resolution: {integrity: sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag==} dom-serializer@2.0.0: - resolution: - { - integrity: sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg== - } + resolution: {integrity: sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==} domelementtype@2.3.0: - resolution: - { - integrity: sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw== - } + resolution: {integrity: sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==} domhandler@4.3.1: - resolution: - { - integrity: sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ== - } - engines: { node: '>= 4' } + resolution: {integrity: sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ==} + engines: {node: '>= 4'} domhandler@5.0.3: - resolution: - { - integrity: sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w== - } - engines: { node: '>= 4' } + resolution: {integrity: sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==} + engines: {node: '>= 4'} domutils@2.8.0: - resolution: - { - integrity: sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A== - } + resolution: {integrity: sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==} domutils@3.2.2: - resolution: - { - integrity: sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw== - } + resolution: {integrity: sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==} download@6.2.5: - resolution: - { - integrity: sha512-DpO9K1sXAST8Cpzb7kmEhogJxymyVUd5qz/vCOSyvwtp2Klj2XcDt5YUuasgxka44SxF0q5RriKIwJmQHG2AuA== - } - engines: { node: '>=4' } + resolution: {integrity: sha512-DpO9K1sXAST8Cpzb7kmEhogJxymyVUd5qz/vCOSyvwtp2Klj2XcDt5YUuasgxka44SxF0q5RriKIwJmQHG2AuA==} + engines: {node: '>=4'} download@7.1.0: - resolution: - { - integrity: sha512-xqnBTVd/E+GxJVrX5/eUJiLYjCGPwMpdL+jGhGU57BvtcA7wwhtHVbXBeUk51kOpW3S7Jn3BQbN9Q1R1Km2qDQ== - } - engines: { node: '>=6' } + resolution: {integrity: sha512-xqnBTVd/E+GxJVrX5/eUJiLYjCGPwMpdL+jGhGU57BvtcA7wwhtHVbXBeUk51kOpW3S7Jn3BQbN9Q1R1Km2qDQ==} + engines: {node: '>=6'} dunder-proto@1.0.1: - resolution: - { - integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A== - } - engines: { node: '>= 0.4' } + resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} + engines: {node: '>= 0.4'} duplexer3@0.1.5: - resolution: - { - integrity: sha512-1A8za6ws41LQgv9HrE/66jyC5yuSjQ3L/KOpFtoBilsAK2iA2wuS5rTt1OCzIvtS2V7nVmedsUU+DGRcjBmOYA== - } + resolution: {integrity: sha512-1A8za6ws41LQgv9HrE/66jyC5yuSjQ3L/KOpFtoBilsAK2iA2wuS5rTt1OCzIvtS2V7nVmedsUU+DGRcjBmOYA==} - electron-to-chromium@1.5.344: - resolution: - { - integrity: sha512-4MxfbmNDm+KPh066EZy+eUnkcDPcZ35wNmOWzFuh/ijvHsve6kbLTLURy88uCNK5FbpN+yk2nQY6BYh1GEt+wg== - } + electron-to-chromium@1.5.349: + resolution: {integrity: sha512-QsWVGyRuY07Aqb234QytTfwd5d9AJlfNIQ5wIOl1L+PZDzI9d9+Fn0FRale/QYlFxt/bUnB0/nLd1jFPGxGK1A==} emoji-regex@10.6.0: - resolution: - { - integrity: sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A== - } + resolution: {integrity: sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==} emoji-regex@8.0.0: - resolution: - { - integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== - } + resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} end-of-stream@1.4.5: - resolution: - { - integrity: sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg== - } + resolution: {integrity: sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==} entities@2.2.0: - resolution: - { - integrity: sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A== - } + resolution: {integrity: sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==} entities@4.5.0: - resolution: - { - integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw== - } - engines: { node: '>=0.12' } + resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==} + engines: {node: '>=0.12'} error-ex@1.3.4: - resolution: - { - integrity: sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ== - } + resolution: {integrity: sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==} es-define-property@1.0.1: - resolution: - { - integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g== - } - engines: { node: '>= 0.4' } + resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==} + engines: {node: '>= 0.4'} es-errors@1.3.0: - resolution: - { - integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw== - } - engines: { node: '>= 0.4' } + resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} + engines: {node: '>= 0.4'} es-object-atoms@1.1.1: - resolution: - { - integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA== - } - engines: { node: '>= 0.4' } + resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==} + engines: {node: '>= 0.4'} esbuild-android-64@0.14.54: - resolution: - { - integrity: sha512-Tz2++Aqqz0rJ7kYBfz+iqyE3QMycD4vk7LBRyWaAVFgFtQ/O8EJOnVmTOiDWYZ/uYzB4kvP+bqejYdVKzE5lAQ== - } - engines: { node: '>=12' } + resolution: {integrity: sha512-Tz2++Aqqz0rJ7kYBfz+iqyE3QMycD4vk7LBRyWaAVFgFtQ/O8EJOnVmTOiDWYZ/uYzB4kvP+bqejYdVKzE5lAQ==} + engines: {node: '>=12'} cpu: [x64] os: [android] esbuild-android-arm64@0.14.54: - resolution: - { - integrity: sha512-F9E+/QDi9sSkLaClO8SOV6etqPd+5DgJje1F9lOWoNncDdOBL2YF59IhsWATSt0TLZbYCf3pNlTHvVV5VfHdvg== - } - engines: { node: '>=12' } + resolution: {integrity: sha512-F9E+/QDi9sSkLaClO8SOV6etqPd+5DgJje1F9lOWoNncDdOBL2YF59IhsWATSt0TLZbYCf3pNlTHvVV5VfHdvg==} + engines: {node: '>=12'} cpu: [arm64] os: [android] esbuild-darwin-64@0.14.54: - resolution: - { - integrity: sha512-jtdKWV3nBviOd5v4hOpkVmpxsBy90CGzebpbO9beiqUYVMBtSc0AL9zGftFuBon7PNDcdvNCEuQqw2x0wP9yug== - } - engines: { node: '>=12' } + resolution: {integrity: sha512-jtdKWV3nBviOd5v4hOpkVmpxsBy90CGzebpbO9beiqUYVMBtSc0AL9zGftFuBon7PNDcdvNCEuQqw2x0wP9yug==} + engines: {node: '>=12'} cpu: [x64] os: [darwin] esbuild-darwin-arm64@0.14.54: - resolution: - { - integrity: sha512-OPafJHD2oUPyvJMrsCvDGkRrVCar5aVyHfWGQzY1dWnzErjrDuSETxwA2HSsyg2jORLY8yBfzc1MIpUkXlctmw== - } - engines: { node: '>=12' } + resolution: {integrity: sha512-OPafJHD2oUPyvJMrsCvDGkRrVCar5aVyHfWGQzY1dWnzErjrDuSETxwA2HSsyg2jORLY8yBfzc1MIpUkXlctmw==} + engines: {node: '>=12'} cpu: [arm64] os: [darwin] esbuild-freebsd-64@0.14.54: - resolution: - { - integrity: sha512-OKwd4gmwHqOTp4mOGZKe/XUlbDJ4Q9TjX0hMPIDBUWWu/kwhBAudJdBoxnjNf9ocIB6GN6CPowYpR/hRCbSYAg== - } - engines: { node: '>=12' } + resolution: {integrity: sha512-OKwd4gmwHqOTp4mOGZKe/XUlbDJ4Q9TjX0hMPIDBUWWu/kwhBAudJdBoxnjNf9ocIB6GN6CPowYpR/hRCbSYAg==} + engines: {node: '>=12'} cpu: [x64] os: [freebsd] esbuild-freebsd-arm64@0.14.54: - resolution: - { - integrity: sha512-sFwueGr7OvIFiQT6WeG0jRLjkjdqWWSrfbVwZp8iMP+8UHEHRBvlaxL6IuKNDwAozNUmbb8nIMXa7oAOARGs1Q== - } - engines: { node: '>=12' } + resolution: {integrity: sha512-sFwueGr7OvIFiQT6WeG0jRLjkjdqWWSrfbVwZp8iMP+8UHEHRBvlaxL6IuKNDwAozNUmbb8nIMXa7oAOARGs1Q==} + engines: {node: '>=12'} cpu: [arm64] os: [freebsd] esbuild-linux-32@0.14.54: - resolution: - { - integrity: sha512-1ZuY+JDI//WmklKlBgJnglpUL1owm2OX+8E1syCD6UAxcMM/XoWd76OHSjl/0MR0LisSAXDqgjT3uJqT67O3qw== - } - engines: { node: '>=12' } + resolution: {integrity: sha512-1ZuY+JDI//WmklKlBgJnglpUL1owm2OX+8E1syCD6UAxcMM/XoWd76OHSjl/0MR0LisSAXDqgjT3uJqT67O3qw==} + engines: {node: '>=12'} cpu: [ia32] os: [linux] esbuild-linux-64@0.14.54: - resolution: - { - integrity: sha512-EgjAgH5HwTbtNsTqQOXWApBaPVdDn7XcK+/PtJwZLT1UmpLoznPd8c5CxqsH2dQK3j05YsB3L17T8vE7cp4cCg== - } - engines: { node: '>=12' } + resolution: {integrity: sha512-EgjAgH5HwTbtNsTqQOXWApBaPVdDn7XcK+/PtJwZLT1UmpLoznPd8c5CxqsH2dQK3j05YsB3L17T8vE7cp4cCg==} + engines: {node: '>=12'} cpu: [x64] os: [linux] esbuild-linux-arm64@0.14.54: - resolution: - { - integrity: sha512-WL71L+0Rwv+Gv/HTmxTEmpv0UgmxYa5ftZILVi2QmZBgX3q7+tDeOQNqGtdXSdsL8TQi1vIaVFHUPDe0O0kdig== - } - engines: { node: '>=12' } + resolution: {integrity: sha512-WL71L+0Rwv+Gv/HTmxTEmpv0UgmxYa5ftZILVi2QmZBgX3q7+tDeOQNqGtdXSdsL8TQi1vIaVFHUPDe0O0kdig==} + engines: {node: '>=12'} cpu: [arm64] os: [linux] esbuild-linux-arm@0.14.54: - resolution: - { - integrity: sha512-qqz/SjemQhVMTnvcLGoLOdFpCYbz4v4fUo+TfsWG+1aOu70/80RV6bgNpR2JCrppV2moUQkww+6bWxXRL9YMGw== - } - engines: { node: '>=12' } + resolution: {integrity: sha512-qqz/SjemQhVMTnvcLGoLOdFpCYbz4v4fUo+TfsWG+1aOu70/80RV6bgNpR2JCrppV2moUQkww+6bWxXRL9YMGw==} + engines: {node: '>=12'} cpu: [arm] os: [linux] esbuild-linux-mips64le@0.14.54: - resolution: - { - integrity: sha512-qTHGQB8D1etd0u1+sB6p0ikLKRVuCWhYQhAHRPkO+OF3I/iSlTKNNS0Lh2Oc0g0UFGguaFZZiPJdJey3AGpAlw== - } - engines: { node: '>=12' } + resolution: {integrity: sha512-qTHGQB8D1etd0u1+sB6p0ikLKRVuCWhYQhAHRPkO+OF3I/iSlTKNNS0Lh2Oc0g0UFGguaFZZiPJdJey3AGpAlw==} + engines: {node: '>=12'} cpu: [mips64el] os: [linux] esbuild-linux-ppc64le@0.14.54: - resolution: - { - integrity: sha512-j3OMlzHiqwZBDPRCDFKcx595XVfOfOnv68Ax3U4UKZ3MTYQB5Yz3X1mn5GnodEVYzhtZgxEBidLWeIs8FDSfrQ== - } - engines: { node: '>=12' } + resolution: {integrity: sha512-j3OMlzHiqwZBDPRCDFKcx595XVfOfOnv68Ax3U4UKZ3MTYQB5Yz3X1mn5GnodEVYzhtZgxEBidLWeIs8FDSfrQ==} + engines: {node: '>=12'} cpu: [ppc64] os: [linux] esbuild-linux-riscv64@0.14.54: - resolution: - { - integrity: sha512-y7Vt7Wl9dkOGZjxQZnDAqqn+XOqFD7IMWiewY5SPlNlzMX39ocPQlOaoxvT4FllA5viyV26/QzHtvTjVNOxHZg== - } - engines: { node: '>=12' } + resolution: {integrity: sha512-y7Vt7Wl9dkOGZjxQZnDAqqn+XOqFD7IMWiewY5SPlNlzMX39ocPQlOaoxvT4FllA5viyV26/QzHtvTjVNOxHZg==} + engines: {node: '>=12'} cpu: [riscv64] os: [linux] esbuild-linux-s390x@0.14.54: - resolution: - { - integrity: sha512-zaHpW9dziAsi7lRcyV4r8dhfG1qBidQWUXweUjnw+lliChJqQr+6XD71K41oEIC3Mx1KStovEmlzm+MkGZHnHA== - } - engines: { node: '>=12' } + resolution: {integrity: sha512-zaHpW9dziAsi7lRcyV4r8dhfG1qBidQWUXweUjnw+lliChJqQr+6XD71K41oEIC3Mx1KStovEmlzm+MkGZHnHA==} + engines: {node: '>=12'} cpu: [s390x] os: [linux] esbuild-netbsd-64@0.14.54: - resolution: - { - integrity: sha512-PR01lmIMnfJTgeU9VJTDY9ZerDWVFIUzAtJuDHwwceppW7cQWjBBqP48NdeRtoP04/AtO9a7w3viI+PIDr6d+w== - } - engines: { node: '>=12' } + resolution: {integrity: sha512-PR01lmIMnfJTgeU9VJTDY9ZerDWVFIUzAtJuDHwwceppW7cQWjBBqP48NdeRtoP04/AtO9a7w3viI+PIDr6d+w==} + engines: {node: '>=12'} cpu: [x64] os: [netbsd] esbuild-openbsd-64@0.14.54: - resolution: - { - integrity: sha512-Qyk7ikT2o7Wu76UsvvDS5q0amJvmRzDyVlL0qf5VLsLchjCa1+IAvd8kTBgUxD7VBUUVgItLkk609ZHUc1oCaw== - } - engines: { node: '>=12' } + resolution: {integrity: sha512-Qyk7ikT2o7Wu76UsvvDS5q0amJvmRzDyVlL0qf5VLsLchjCa1+IAvd8kTBgUxD7VBUUVgItLkk609ZHUc1oCaw==} + engines: {node: '>=12'} cpu: [x64] os: [openbsd] esbuild-sunos-64@0.14.54: - resolution: - { - integrity: sha512-28GZ24KmMSeKi5ueWzMcco6EBHStL3B6ubM7M51RmPwXQGLe0teBGJocmWhgwccA1GeFXqxzILIxXpHbl9Q/Kw== - } - engines: { node: '>=12' } + resolution: {integrity: sha512-28GZ24KmMSeKi5ueWzMcco6EBHStL3B6ubM7M51RmPwXQGLe0teBGJocmWhgwccA1GeFXqxzILIxXpHbl9Q/Kw==} + engines: {node: '>=12'} cpu: [x64] os: [sunos] esbuild-windows-32@0.14.54: - resolution: - { - integrity: sha512-T+rdZW19ql9MjS7pixmZYVObd9G7kcaZo+sETqNH4RCkuuYSuv9AGHUVnPoP9hhuE1WM1ZimHz1CIBHBboLU7w== - } - engines: { node: '>=12' } + resolution: {integrity: sha512-T+rdZW19ql9MjS7pixmZYVObd9G7kcaZo+sETqNH4RCkuuYSuv9AGHUVnPoP9hhuE1WM1ZimHz1CIBHBboLU7w==} + engines: {node: '>=12'} cpu: [ia32] os: [win32] esbuild-windows-64@0.14.54: - resolution: - { - integrity: sha512-AoHTRBUuYwXtZhjXZbA1pGfTo8cJo3vZIcWGLiUcTNgHpJJMC1rVA44ZereBHMJtotyN71S8Qw0npiCIkW96cQ== - } - engines: { node: '>=12' } + resolution: {integrity: sha512-AoHTRBUuYwXtZhjXZbA1pGfTo8cJo3vZIcWGLiUcTNgHpJJMC1rVA44ZereBHMJtotyN71S8Qw0npiCIkW96cQ==} + engines: {node: '>=12'} cpu: [x64] os: [win32] esbuild-windows-arm64@0.14.54: - resolution: - { - integrity: sha512-M0kuUvXhot1zOISQGXwWn6YtS+Y/1RT9WrVIOywZnJHo3jCDyewAc79aKNQWFCQm+xNHVTq9h8dZKvygoXQQRg== - } - engines: { node: '>=12' } + resolution: {integrity: sha512-M0kuUvXhot1zOISQGXwWn6YtS+Y/1RT9WrVIOywZnJHo3jCDyewAc79aKNQWFCQm+xNHVTq9h8dZKvygoXQQRg==} + engines: {node: '>=12'} cpu: [arm64] os: [win32] esbuild@0.14.54: - resolution: - { - integrity: sha512-Cy9llcy8DvET5uznocPyqL3BFRrFXSVqbgpMJ9Wz8oVjZlh/zUSNbPRbov0VX7VxN2JH1Oa0uNxZ7eLRb62pJA== - } - engines: { node: '>=12' } + resolution: {integrity: sha512-Cy9llcy8DvET5uznocPyqL3BFRrFXSVqbgpMJ9Wz8oVjZlh/zUSNbPRbov0VX7VxN2JH1Oa0uNxZ7eLRb62pJA==} + engines: {node: '>=12'} hasBin: true esbuild@0.27.4: - resolution: - { - integrity: sha512-Rq4vbHnYkK5fws5NF7MYTU68FPRE1ajX7heQ/8QXXWqNgqqJ/GkmmyxIzUnf2Sr/bakf8l54716CcMGHYhMrrQ== - } - engines: { node: '>=18' } + resolution: {integrity: sha512-Rq4vbHnYkK5fws5NF7MYTU68FPRE1ajX7heQ/8QXXWqNgqqJ/GkmmyxIzUnf2Sr/bakf8l54716CcMGHYhMrrQ==} + engines: {node: '>=18'} hasBin: true escalade@3.2.0: - resolution: - { - integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA== - } - engines: { node: '>=6' } + resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} + engines: {node: '>=6'} escape-string-regexp@1.0.5: - resolution: - { - integrity: sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg== - } - engines: { node: '>=0.8.0' } + resolution: {integrity: sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==} + engines: {node: '>=0.8.0'} escape-string-regexp@4.0.0: - resolution: - { - integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== - } - engines: { node: '>=10' } + resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} + engines: {node: '>=10'} eslint-config-prettier@10.1.8: - resolution: - { - integrity: sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w== - } + resolution: {integrity: sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w==} hasBin: true peerDependencies: eslint: '>=7.0.0' eslint-scope@9.1.2: - resolution: - { - integrity: sha512-xS90H51cKw0jltxmvmHy2Iai1LIqrfbw57b79w/J7MfvDfkIkFZ+kj6zC3BjtUwh150HsSSdxXZcsuv72miDFQ== - } - engines: { node: ^20.19.0 || ^22.13.0 || >=24 } + resolution: {integrity: sha512-xS90H51cKw0jltxmvmHy2Iai1LIqrfbw57b79w/J7MfvDfkIkFZ+kj6zC3BjtUwh150HsSSdxXZcsuv72miDFQ==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} eslint-visitor-keys@3.4.3: - resolution: - { - integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag== - } - engines: { node: ^12.22.0 || ^14.17.0 || >=16.0.0 } + resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} eslint-visitor-keys@5.0.1: - resolution: - { - integrity: sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA== - } - engines: { node: ^20.19.0 || ^22.13.0 || >=24 } + resolution: {integrity: sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} - eslint@10.2.1: - resolution: - { - integrity: sha512-wiyGaKsDgqXvF40P8mDwiUp/KQjE1FdrIEJsM8PZ3XCiniTMXS3OHWWUe5FI5agoCnr8x4xPrTDZuxsBlNHl+Q== - } - engines: { node: ^20.19.0 || ^22.13.0 || >=24 } + eslint@10.3.0: + resolution: {integrity: sha512-XbEXaRva5cF0ZQB8w6MluHA0kZZfV2DuCMJ3ozyEOHLwDpZX2Lmm/7Pp0xdJmI0GL1W05VH5VwIFHEm1Vcw2gw==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} hasBin: true peerDependencies: jiti: '*' @@ -2685,159 +1688,90 @@ packages: optional: true espree@11.2.0: - resolution: - { - integrity: sha512-7p3DrVEIopW1B1avAGLuCSh1jubc01H2JHc8B4qqGblmg5gI9yumBgACjWo4JlIc04ufug4xJ3SQI8HkS/Rgzw== - } - engines: { node: ^20.19.0 || ^22.13.0 || >=24 } + resolution: {integrity: sha512-7p3DrVEIopW1B1avAGLuCSh1jubc01H2JHc8B4qqGblmg5gI9yumBgACjWo4JlIc04ufug4xJ3SQI8HkS/Rgzw==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} esquery@1.7.0: - resolution: - { - integrity: sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g== - } - engines: { node: '>=0.10' } + resolution: {integrity: sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==} + engines: {node: '>=0.10'} esrecurse@4.3.0: - resolution: - { - integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag== - } - engines: { node: '>=4.0' } + resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} + engines: {node: '>=4.0'} estraverse@5.3.0: - resolution: - { - integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA== - } - engines: { node: '>=4.0' } + resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} + engines: {node: '>=4.0'} estree-walker@2.0.2: - resolution: - { - integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w== - } + resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==} esutils@2.0.3: - resolution: - { - integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== - } - engines: { node: '>=0.10.0' } + resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} + engines: {node: '>=0.10.0'} etag@1.8.1: - resolution: - { - integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg== - } - engines: { node: '>= 0.6' } + resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==} + engines: {node: '>= 0.6'} exec-buffer@3.2.0: - resolution: - { - integrity: sha512-wsiD+2Tp6BWHoVv3B+5Dcx6E7u5zky+hUwOHjuH2hKSLR3dvRmX8fk8UD8uqQixHs4Wk6eDmiegVrMPjKj7wpA== - } - engines: { node: '>=4' } + resolution: {integrity: sha512-wsiD+2Tp6BWHoVv3B+5Dcx6E7u5zky+hUwOHjuH2hKSLR3dvRmX8fk8UD8uqQixHs4Wk6eDmiegVrMPjKj7wpA==} + engines: {node: '>=4'} execa@0.7.0: - resolution: - { - integrity: sha512-RztN09XglpYI7aBBrJCPW95jEH7YF1UEPOoX9yDhUTPdp7mK+CQvnLTuD10BNXZ3byLTu2uehZ8EcKT/4CGiFw== - } - engines: { node: '>=4' } + resolution: {integrity: sha512-RztN09XglpYI7aBBrJCPW95jEH7YF1UEPOoX9yDhUTPdp7mK+CQvnLTuD10BNXZ3byLTu2uehZ8EcKT/4CGiFw==} + engines: {node: '>=4'} execa@1.0.0: - resolution: - { - integrity: sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA== - } - engines: { node: '>=6' } + resolution: {integrity: sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==} + engines: {node: '>=6'} execa@4.1.0: - resolution: - { - integrity: sha512-j5W0//W7f8UxAn8hXVnwG8tLwdiUy4FJLcSupCg6maBYZDpyBvTApK7KyuI4bKj8KOh1r2YH+6ucuYtJv1bTZA== - } - engines: { node: '>=10' } + resolution: {integrity: sha512-j5W0//W7f8UxAn8hXVnwG8tLwdiUy4FJLcSupCg6maBYZDpyBvTApK7KyuI4bKj8KOh1r2YH+6ucuYtJv1bTZA==} + engines: {node: '>=10'} execa@5.1.1: - resolution: - { - integrity: sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg== - } - engines: { node: '>=10' } + resolution: {integrity: sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==} + engines: {node: '>=10'} executable@4.1.1: - resolution: - { - integrity: sha512-8iA79xD3uAch729dUG8xaaBBFGaEa0wdD2VkYLFHwlqosEj/jT66AzcreRDSgV7ehnNLBW2WR5jIXwGKjVdTLg== - } - engines: { node: '>=4' } + resolution: {integrity: sha512-8iA79xD3uAch729dUG8xaaBBFGaEa0wdD2VkYLFHwlqosEj/jT66AzcreRDSgV7ehnNLBW2WR5jIXwGKjVdTLg==} + engines: {node: '>=4'} ext-list@2.2.2: - resolution: - { - integrity: sha512-u+SQgsubraE6zItfVA0tBuCBhfU9ogSRnsvygI7wht9TS510oLkBRXBsqopeUG/GBOIQyKZO9wjTqIu/sf5zFA== - } - engines: { node: '>=0.10.0' } + resolution: {integrity: sha512-u+SQgsubraE6zItfVA0tBuCBhfU9ogSRnsvygI7wht9TS510oLkBRXBsqopeUG/GBOIQyKZO9wjTqIu/sf5zFA==} + engines: {node: '>=0.10.0'} ext-name@5.0.0: - resolution: - { - integrity: sha512-yblEwXAbGv1VQDmow7s38W77hzAgJAO50ztBLMcUyUBfxv1HC+LGwtiEN+Co6LtlqT/5uwVOxsD4TNIilWhwdQ== - } - engines: { node: '>=4' } + resolution: {integrity: sha512-yblEwXAbGv1VQDmow7s38W77hzAgJAO50ztBLMcUyUBfxv1HC+LGwtiEN+Co6LtlqT/5uwVOxsD4TNIilWhwdQ==} + engines: {node: '>=4'} fast-deep-equal@3.1.3: - resolution: - { - integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== - } + resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} fast-glob@3.3.3: - resolution: - { - integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg== - } - engines: { node: '>=8.6.0' } + resolution: {integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==} + engines: {node: '>=8.6.0'} fast-json-stable-stringify@2.1.0: - resolution: - { - integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== - } + resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} fast-levenshtein@2.0.6: - resolution: - { - integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw== - } + resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} fast-xml-parser@4.5.6: - resolution: - { - integrity: sha512-Yd4vkROfJf8AuJrDIVMVmYfULKmIJszVsMv7Vo71aocsKgFxpdlpSHXSaInvyYfgw2PRuObQSW2GFpVMUjxu9A== - } + resolution: {integrity: sha512-Yd4vkROfJf8AuJrDIVMVmYfULKmIJszVsMv7Vo71aocsKgFxpdlpSHXSaInvyYfgw2PRuObQSW2GFpVMUjxu9A==} hasBin: true fastq@1.20.1: - resolution: - { - integrity: sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw== - } + resolution: {integrity: sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==} fd-slicer@1.1.0: - resolution: - { - integrity: sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g== - } + resolution: {integrity: sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==} fdir@6.5.0: - resolution: - { - integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg== - } - engines: { node: '>=12.0.0' } + resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} + engines: {node: '>=12.0.0'} peerDependencies: picomatch: ^3 || ^4 peerDependenciesMeta: @@ -2845,1727 +1779,977 @@ packages: optional: true figures@1.7.0: - resolution: - { - integrity: sha512-UxKlfCRuCBxSXU4C6t9scbDyWZ4VlaFFdojKtzJuSkuOBQ5CNFum+zZXFwHjo+CxBC1t6zlYPgHIgFjL8ggoEQ== - } - engines: { node: '>=0.10.0' } + resolution: {integrity: sha512-UxKlfCRuCBxSXU4C6t9scbDyWZ4VlaFFdojKtzJuSkuOBQ5CNFum+zZXFwHjo+CxBC1t6zlYPgHIgFjL8ggoEQ==} + engines: {node: '>=0.10.0'} file-entry-cache@8.0.0: - resolution: - { - integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ== - } - engines: { node: '>=16.0.0' } + resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==} + engines: {node: '>=16.0.0'} file-type@10.11.0: - resolution: - { - integrity: sha512-uzk64HRpUZyTGZtVuvrjP0FYxzQrBf4rojot6J65YMEbwBLB0CWm0CLojVpwpmFmxcE/lkvYICgfcGozbBq6rw== - } - engines: { node: '>=6' } + resolution: {integrity: sha512-uzk64HRpUZyTGZtVuvrjP0FYxzQrBf4rojot6J65YMEbwBLB0CWm0CLojVpwpmFmxcE/lkvYICgfcGozbBq6rw==} + engines: {node: '>=6'} file-type@12.4.2: - resolution: - { - integrity: sha512-UssQP5ZgIOKelfsaB5CuGAL+Y+q7EmONuiwF3N5HAH0t27rvrttgi6Ra9k/+DVaY9UF6+ybxu5pOXLUdA8N7Vg== - } - engines: { node: '>=8' } + resolution: {integrity: sha512-UssQP5ZgIOKelfsaB5CuGAL+Y+q7EmONuiwF3N5HAH0t27rvrttgi6Ra9k/+DVaY9UF6+ybxu5pOXLUdA8N7Vg==} + engines: {node: '>=8'} file-type@3.9.0: - resolution: - { - integrity: sha512-RLoqTXE8/vPmMuTI88DAzhMYC99I8BWv7zYP4A1puo5HIjEJ5EX48ighy4ZyKMG9EDXxBgW6e++cn7d1xuFghA== - } - engines: { node: '>=0.10.0' } + resolution: {integrity: sha512-RLoqTXE8/vPmMuTI88DAzhMYC99I8BWv7zYP4A1puo5HIjEJ5EX48ighy4ZyKMG9EDXxBgW6e++cn7d1xuFghA==} + engines: {node: '>=0.10.0'} file-type@4.4.0: - resolution: - { - integrity: sha512-f2UbFQEk7LXgWpi5ntcO86OeA/cC80fuDDDaX/fZ2ZGel+AF7leRQqBBW1eJNiiQkrZlAoM6P+VYP5P6bOlDEQ== - } - engines: { node: '>=4' } + resolution: {integrity: sha512-f2UbFQEk7LXgWpi5ntcO86OeA/cC80fuDDDaX/fZ2ZGel+AF7leRQqBBW1eJNiiQkrZlAoM6P+VYP5P6bOlDEQ==} + engines: {node: '>=4'} file-type@5.2.0: - resolution: - { - integrity: sha512-Iq1nJ6D2+yIO4c8HHg4fyVb8mAJieo1Oloy1mLLaB2PvezNedhBVm+QU7g0qM42aiMbRXTxKKwGD17rjKNJYVQ== - } - engines: { node: '>=4' } + resolution: {integrity: sha512-Iq1nJ6D2+yIO4c8HHg4fyVb8mAJieo1Oloy1mLLaB2PvezNedhBVm+QU7g0qM42aiMbRXTxKKwGD17rjKNJYVQ==} + engines: {node: '>=4'} file-type@6.2.0: - resolution: - { - integrity: sha512-YPcTBDV+2Tm0VqjybVd32MHdlEGAtuxS3VAYsumFokDSMG+ROT5wawGlnHDoz7bfMcMDt9hxuXvXwoKUx2fkOg== - } - engines: { node: '>=4' } + resolution: {integrity: sha512-YPcTBDV+2Tm0VqjybVd32MHdlEGAtuxS3VAYsumFokDSMG+ROT5wawGlnHDoz7bfMcMDt9hxuXvXwoKUx2fkOg==} + engines: {node: '>=4'} file-type@8.1.0: - resolution: - { - integrity: sha512-qyQ0pzAy78gVoJsmYeNgl8uH8yKhr1lVhW7JbzJmnlRi0I4R2eEDEJZVKG8agpDnLpacwNbDhLNG/LMdxHD2YQ== - } - engines: { node: '>=6' } + resolution: {integrity: sha512-qyQ0pzAy78gVoJsmYeNgl8uH8yKhr1lVhW7JbzJmnlRi0I4R2eEDEJZVKG8agpDnLpacwNbDhLNG/LMdxHD2YQ==} + engines: {node: '>=6'} filename-reserved-regex@2.0.0: - resolution: - { - integrity: sha512-lc1bnsSr4L4Bdif8Xb/qrtokGbq5zlsms/CYH8PP+WtCkGNF65DPiQY8vG3SakEdRn8Dlnm+gW/qWKKjS5sZzQ== - } - engines: { node: '>=4' } + resolution: {integrity: sha512-lc1bnsSr4L4Bdif8Xb/qrtokGbq5zlsms/CYH8PP+WtCkGNF65DPiQY8vG3SakEdRn8Dlnm+gW/qWKKjS5sZzQ==} + engines: {node: '>=4'} filenamify@2.1.0: - resolution: - { - integrity: sha512-ICw7NTT6RsDp2rnYKVd8Fu4cr6ITzGy3+u4vUujPkabyaz+03F24NWEX7fs5fp+kBonlaqPH8fAO2NM+SXt/JA== - } - engines: { node: '>=4' } + resolution: {integrity: sha512-ICw7NTT6RsDp2rnYKVd8Fu4cr6ITzGy3+u4vUujPkabyaz+03F24NWEX7fs5fp+kBonlaqPH8fAO2NM+SXt/JA==} + engines: {node: '>=4'} fill-range@7.1.1: - resolution: - { - integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg== - } - engines: { node: '>=8' } + resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} + engines: {node: '>=8'} find-root@1.1.0: - resolution: - { - integrity: sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng== - } + resolution: {integrity: sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==} find-up@1.1.2: - resolution: - { - integrity: sha512-jvElSjyuo4EMQGoTwo1uJU5pQMwTW5lS1x05zzfJuTIyLR3zwO27LYrxNg+dlvKpGOuGy/MzBdXh80g0ve5+HA== - } - engines: { node: '>=0.10.0' } + resolution: {integrity: sha512-jvElSjyuo4EMQGoTwo1uJU5pQMwTW5lS1x05zzfJuTIyLR3zwO27LYrxNg+dlvKpGOuGy/MzBdXh80g0ve5+HA==} + engines: {node: '>=0.10.0'} find-up@5.0.0: - resolution: - { - integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng== - } - engines: { node: '>=10' } + resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} + engines: {node: '>=10'} find-versions@3.2.0: - resolution: - { - integrity: sha512-P8WRou2S+oe222TOCHitLy8zj+SIsVJh52VP4lvXkaFVnOFFdoWv1H1Jjvel1aI6NCFOAaeAVm8qrI0odiLcww== - } - engines: { node: '>=6' } + resolution: {integrity: sha512-P8WRou2S+oe222TOCHitLy8zj+SIsVJh52VP4lvXkaFVnOFFdoWv1H1Jjvel1aI6NCFOAaeAVm8qrI0odiLcww==} + engines: {node: '>=6'} flat-cache@4.0.1: - resolution: - { - integrity: sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw== - } - engines: { node: '>=16' } + resolution: {integrity: sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==} + engines: {node: '>=16'} flatted@3.4.2: - resolution: - { - integrity: sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA== - } + resolution: {integrity: sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==} for-each@0.3.5: - resolution: - { - integrity: sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg== - } - engines: { node: '>= 0.4' } + resolution: {integrity: sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==} + engines: {node: '>= 0.4'} from2@2.3.0: - resolution: - { - integrity: sha512-OMcX/4IC/uqEPVgGeyfN22LJk6AZrMkRZHxcHBMBvHScDGgwTm2GT2Wkgtocyd3JfZffjj2kYUDXXII0Fk9W0g== - } + resolution: {integrity: sha512-OMcX/4IC/uqEPVgGeyfN22LJk6AZrMkRZHxcHBMBvHScDGgwTm2GT2Wkgtocyd3JfZffjj2kYUDXXII0Fk9W0g==} fs-constants@1.0.0: - resolution: - { - integrity: sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow== - } + resolution: {integrity: sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==} fs-extra@10.1.0: - resolution: - { - integrity: sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ== - } - engines: { node: '>=12' } + resolution: {integrity: sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==} + engines: {node: '>=12'} fs.realpath@1.0.0: - resolution: - { - integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw== - } + resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} fsevents@2.3.3: - resolution: - { - integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw== - } - engines: { node: ^8.16.0 || ^10.6.0 || >=11.0.0 } + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} os: [darwin] function-bind@1.1.2: - resolution: - { - integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA== - } + resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} gensync@1.0.0-beta.2: - resolution: - { - integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg== - } - engines: { node: '>=6.9.0' } + resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} + engines: {node: '>=6.9.0'} get-caller-file@2.0.5: - resolution: - { - integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== - } - engines: { node: 6.* || 8.* || >= 10.* } + resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} + engines: {node: 6.* || 8.* || >= 10.*} get-east-asian-width@1.5.0: - resolution: - { - integrity: sha512-CQ+bEO+Tva/qlmw24dCejulK5pMzVnUOFOijVogd3KQs07HnRIgp8TGipvCCRT06xeYEbpbgwaCxglFyiuIcmA== - } - engines: { node: '>=18' } + resolution: {integrity: sha512-CQ+bEO+Tva/qlmw24dCejulK5pMzVnUOFOijVogd3KQs07HnRIgp8TGipvCCRT06xeYEbpbgwaCxglFyiuIcmA==} + engines: {node: '>=18'} get-intrinsic@1.3.0: - resolution: - { - integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ== - } - engines: { node: '>= 0.4' } + resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} + engines: {node: '>= 0.4'} get-proto@1.0.1: - resolution: - { - integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g== - } - engines: { node: '>= 0.4' } + resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} + engines: {node: '>= 0.4'} get-proxy@2.1.0: - resolution: - { - integrity: sha512-zmZIaQTWnNQb4R4fJUEp/FC51eZsc6EkErspy3xtIYStaq8EB/hDIWipxsal+E8rz0qD7f2sL/NA9Xee4RInJw== - } - engines: { node: '>=4' } + resolution: {integrity: sha512-zmZIaQTWnNQb4R4fJUEp/FC51eZsc6EkErspy3xtIYStaq8EB/hDIWipxsal+E8rz0qD7f2sL/NA9Xee4RInJw==} + engines: {node: '>=4'} get-stdin@4.0.1: - resolution: - { - integrity: sha512-F5aQMywwJ2n85s4hJPTT9RPxGmubonuB10MNYo17/xph174n2MIR33HRguhzVag10O/npM7SPk73LMZNP+FaWw== - } - engines: { node: '>=0.10.0' } + resolution: {integrity: sha512-F5aQMywwJ2n85s4hJPTT9RPxGmubonuB10MNYo17/xph174n2MIR33HRguhzVag10O/npM7SPk73LMZNP+FaWw==} + engines: {node: '>=0.10.0'} get-stream@2.3.1: - resolution: - { - integrity: sha512-AUGhbbemXxrZJRD5cDvKtQxLuYaIbNtDTK8YqupCI393Q2KSTreEsLUN3ZxAWFGiKTzL6nKuzfcIvieflUX9qA== - } - engines: { node: '>=0.10.0' } + resolution: {integrity: sha512-AUGhbbemXxrZJRD5cDvKtQxLuYaIbNtDTK8YqupCI393Q2KSTreEsLUN3ZxAWFGiKTzL6nKuzfcIvieflUX9qA==} + engines: {node: '>=0.10.0'} get-stream@3.0.0: - resolution: - { - integrity: sha512-GlhdIUuVakc8SJ6kK0zAFbiGzRFzNnY4jUuEbV9UROo4Y+0Ny4fjvcZFVTeDA4odpFyOQzaw6hXukJSq/f28sQ== - } - engines: { node: '>=4' } + resolution: {integrity: sha512-GlhdIUuVakc8SJ6kK0zAFbiGzRFzNnY4jUuEbV9UROo4Y+0Ny4fjvcZFVTeDA4odpFyOQzaw6hXukJSq/f28sQ==} + engines: {node: '>=4'} get-stream@4.1.0: - resolution: - { - integrity: sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w== - } - engines: { node: '>=6' } + resolution: {integrity: sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==} + engines: {node: '>=6'} get-stream@5.2.0: - resolution: - { - integrity: sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA== - } - engines: { node: '>=8' } + resolution: {integrity: sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==} + engines: {node: '>=8'} get-stream@6.0.1: - resolution: - { - integrity: sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg== - } - engines: { node: '>=10' } + resolution: {integrity: sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==} + engines: {node: '>=10'} gifsicle@5.2.0: - resolution: - { - integrity: sha512-vOIS3j0XoTCxq9pkGj43gEix82RkI5FveNgaFZutjbaui/HH+4fR8Y56dwXDuxYo8hR4xOo6/j2h1WHoQW6XLw== - } - engines: { node: '>=10' } + resolution: {integrity: sha512-vOIS3j0XoTCxq9pkGj43gEix82RkI5FveNgaFZutjbaui/HH+4fR8Y56dwXDuxYo8hR4xOo6/j2h1WHoQW6XLw==} + engines: {node: '>=10'} hasBin: true glob-parent@5.1.2: - resolution: - { - integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== - } - engines: { node: '>= 6' } + resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} + engines: {node: '>= 6'} glob-parent@6.0.2: - resolution: - { - integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A== - } - engines: { node: '>=10.13.0' } + resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} + engines: {node: '>=10.13.0'} glob@7.2.3: - resolution: - { - integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q== - } + resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me globby@10.0.2: - resolution: - { - integrity: sha512-7dUi7RvCoT/xast/o/dLN53oqND4yk0nsHkhRgn9w65C4PofCLOoJ39iSOg+qVDdWQPIEj+eszMHQ+aLVwwQSg== - } - engines: { node: '>=8' } + resolution: {integrity: sha512-7dUi7RvCoT/xast/o/dLN53oqND4yk0nsHkhRgn9w65C4PofCLOoJ39iSOg+qVDdWQPIEj+eszMHQ+aLVwwQSg==} + engines: {node: '>=8'} gopd@1.2.0: - resolution: - { - integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg== - } - engines: { node: '>= 0.4' } + resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} + engines: {node: '>= 0.4'} got@7.1.0: - resolution: - { - integrity: sha512-Y5WMo7xKKq1muPsxD+KmrR8DH5auG7fBdDVueZwETwV6VytKyU9OX/ddpq2/1hp1vIPvVb4T81dKQz3BivkNLw== - } - engines: { node: '>=4' } + resolution: {integrity: sha512-Y5WMo7xKKq1muPsxD+KmrR8DH5auG7fBdDVueZwETwV6VytKyU9OX/ddpq2/1hp1vIPvVb4T81dKQz3BivkNLw==} + engines: {node: '>=4'} got@8.3.2: - resolution: - { - integrity: sha512-qjUJ5U/hawxosMryILofZCkm3C84PLJS/0grRIpjAwu+Lkxxj5cxeCU25BG0/3mDSpXKTyZr8oh8wIgLaH0QCw== - } - engines: { node: '>=4' } + resolution: {integrity: sha512-qjUJ5U/hawxosMryILofZCkm3C84PLJS/0grRIpjAwu+Lkxxj5cxeCU25BG0/3mDSpXKTyZr8oh8wIgLaH0QCw==} + engines: {node: '>=4'} graceful-fs@4.2.11: - resolution: - { - integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ== - } + resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} has-ansi@2.0.0: - resolution: - { - integrity: sha512-C8vBJ8DwUCx19vhm7urhTuUsr4/IyP6l4VzNQDv+ryHQObW3TTTp9yB68WpYgRe2bbaGuZ/se74IqFeVnMnLZg== - } - engines: { node: '>=0.10.0' } + resolution: {integrity: sha512-C8vBJ8DwUCx19vhm7urhTuUsr4/IyP6l4VzNQDv+ryHQObW3TTTp9yB68WpYgRe2bbaGuZ/se74IqFeVnMnLZg==} + engines: {node: '>=0.10.0'} has-flag@4.0.0: - resolution: - { - integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== - } - engines: { node: '>=8' } + resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} + engines: {node: '>=8'} has-property-descriptors@1.0.2: - resolution: - { - integrity: sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg== - } + resolution: {integrity: sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==} has-symbol-support-x@1.4.2: - resolution: - { - integrity: sha512-3ToOva++HaW+eCpgqZrCfN51IPB+7bJNVT6CUATzueB5Heb8o6Nam0V3HG5dlDvZU1Gn5QLcbahiKw/XVk5JJw== - } + resolution: {integrity: sha512-3ToOva++HaW+eCpgqZrCfN51IPB+7bJNVT6CUATzueB5Heb8o6Nam0V3HG5dlDvZU1Gn5QLcbahiKw/XVk5JJw==} has-symbols@1.1.0: - resolution: - { - integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ== - } - engines: { node: '>= 0.4' } + resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==} + engines: {node: '>= 0.4'} has-to-string-tag-x@1.4.1: - resolution: - { - integrity: sha512-vdbKfmw+3LoOYVr+mtxHaX5a96+0f3DljYd8JOqvOLsf5mw2Otda2qCDT9qRqLAhrjyQ0h7ual5nOiASpsGNFw== - } + resolution: {integrity: sha512-vdbKfmw+3LoOYVr+mtxHaX5a96+0f3DljYd8JOqvOLsf5mw2Otda2qCDT9qRqLAhrjyQ0h7ual5nOiASpsGNFw==} has-tostringtag@1.0.2: - resolution: - { - integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw== - } - engines: { node: '>= 0.4' } + resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==} + engines: {node: '>= 0.4'} hasown@2.0.3: - resolution: - { - integrity: sha512-ej4AhfhfL2Q2zpMmLo7U1Uv9+PyhIZpgQLGT1F9miIGmiCJIoCgSmczFdrc97mWT4kVY72KA+WnnhJ5pghSvSg== - } - engines: { node: '>= 0.4' } + resolution: {integrity: sha512-ej4AhfhfL2Q2zpMmLo7U1Uv9+PyhIZpgQLGT1F9miIGmiCJIoCgSmczFdrc97mWT4kVY72KA+WnnhJ5pghSvSg==} + engines: {node: '>= 0.4'} he@1.2.0: - resolution: - { - integrity: sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw== - } + resolution: {integrity: sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==} hasBin: true hoist-non-react-statics@3.3.2: - resolution: - { - integrity: sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw== - } + resolution: {integrity: sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==} hosted-git-info@2.8.9: - resolution: - { - integrity: sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw== - } + resolution: {integrity: sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==} http-cache-semantics@3.8.1: - resolution: - { - integrity: sha512-5ai2iksyV8ZXmnZhHH4rWPoxxistEexSi5936zIQ1bnNTW5VnA85B6P/VpXiRM017IgRvb2kKo1a//y+0wSp3w== - } + resolution: {integrity: sha512-5ai2iksyV8ZXmnZhHH4rWPoxxistEexSi5936zIQ1bnNTW5VnA85B6P/VpXiRM017IgRvb2kKo1a//y+0wSp3w==} human-signals@1.1.1: - resolution: - { - integrity: sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw== - } - engines: { node: '>=8.12.0' } + resolution: {integrity: sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw==} + engines: {node: '>=8.12.0'} human-signals@2.1.0: - resolution: - { - integrity: sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw== - } - engines: { node: '>=10.17.0' } + resolution: {integrity: sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==} + engines: {node: '>=10.17.0'} ieee754@1.2.1: - resolution: - { - integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA== - } + resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} ignore@5.3.2: - resolution: - { - integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g== - } - engines: { node: '>= 4' } + resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} + engines: {node: '>= 4'} ignore@7.0.5: - resolution: - { - integrity: sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg== - } - engines: { node: '>= 4' } + resolution: {integrity: sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==} + engines: {node: '>= 4'} imagemin-gifsicle@7.0.0: - resolution: - { - integrity: sha512-LaP38xhxAwS3W8PFh4y5iQ6feoTSF+dTAXFRUEYQWYst6Xd+9L/iPk34QGgK/VO/objmIlmq9TStGfVY2IcHIA== - } - engines: { node: '>=10' } + resolution: {integrity: sha512-LaP38xhxAwS3W8PFh4y5iQ6feoTSF+dTAXFRUEYQWYst6Xd+9L/iPk34QGgK/VO/objmIlmq9TStGfVY2IcHIA==} + engines: {node: '>=10'} imagemin-jpegtran@7.0.0: - resolution: - { - integrity: sha512-MJoyTCW8YjMJf56NorFE41SR/WkaGA3IYk4JgvMlRwguJEEd3PnP9UxA8Y2UWjquz8d+On3Ds/03ZfiiLS8xTQ== - } - engines: { node: '>=10' } + resolution: {integrity: sha512-MJoyTCW8YjMJf56NorFE41SR/WkaGA3IYk4JgvMlRwguJEEd3PnP9UxA8Y2UWjquz8d+On3Ds/03ZfiiLS8xTQ==} + engines: {node: '>=10'} imagemin-mozjpeg@9.0.0: - resolution: - { - integrity: sha512-TwOjTzYqCFRgROTWpVSt5UTT0JeCuzF1jswPLKALDd89+PmrJ2PdMMYeDLYZ1fs9cTovI9GJd68mRSnuVt691w== - } - engines: { node: '>=10' } + resolution: {integrity: sha512-TwOjTzYqCFRgROTWpVSt5UTT0JeCuzF1jswPLKALDd89+PmrJ2PdMMYeDLYZ1fs9cTovI9GJd68mRSnuVt691w==} + engines: {node: '>=10'} imagemin-optipng@8.0.0: - resolution: - { - integrity: sha512-CUGfhfwqlPjAC0rm8Fy+R2DJDBGjzy2SkfyT09L8rasnF9jSoHFqJ1xxSZWK6HVPZBMhGPMxCTL70OgTHlLF5A== - } - engines: { node: '>=10' } + resolution: {integrity: sha512-CUGfhfwqlPjAC0rm8Fy+R2DJDBGjzy2SkfyT09L8rasnF9jSoHFqJ1xxSZWK6HVPZBMhGPMxCTL70OgTHlLF5A==} + engines: {node: '>=10'} imagemin-pngquant@9.0.2: - resolution: - { - integrity: sha512-cj//bKo8+Frd/DM8l6Pg9pws1pnDUjgb7ae++sUX1kUVdv2nrngPykhiUOgFeE0LGY/LmUbCf4egCHC4YUcZSg== - } - engines: { node: '>=10' } + resolution: {integrity: sha512-cj//bKo8+Frd/DM8l6Pg9pws1pnDUjgb7ae++sUX1kUVdv2nrngPykhiUOgFeE0LGY/LmUbCf4egCHC4YUcZSg==} + engines: {node: '>=10'} imagemin-svgo@9.0.0: - resolution: - { - integrity: sha512-uNgXpKHd99C0WODkrJ8OO/3zW3qjgS4pW7hcuII0RcHN3tnKxDjJWcitdVC/TZyfIqSricU8WfrHn26bdSW62g== - } - engines: { node: '>=10' } + resolution: {integrity: sha512-uNgXpKHd99C0WODkrJ8OO/3zW3qjgS4pW7hcuII0RcHN3tnKxDjJWcitdVC/TZyfIqSricU8WfrHn26bdSW62g==} + engines: {node: '>=10'} imagemin-webp@6.1.0: - resolution: - { - integrity: sha512-i8ZluZV1pfQX9aVzmZ/VZh9KBSdPwUlp5VruAa9c30GZnX/nMl5n7h+oUMnI7Mg7+SUpu9mYBsw2nsYGUEllWQ== - } - engines: { node: '>=10' } + resolution: {integrity: sha512-i8ZluZV1pfQX9aVzmZ/VZh9KBSdPwUlp5VruAa9c30GZnX/nMl5n7h+oUMnI7Mg7+SUpu9mYBsw2nsYGUEllWQ==} + engines: {node: '>=10'} imagemin@7.0.1: - resolution: - { - integrity: sha512-33AmZ+xjZhg2JMCe+vDf6a9mzWukE7l+wAtesjE7KyteqqKjzxv7aVQeWnul1Ve26mWvEQqyPwl0OctNBfSR9w== - } - engines: { node: '>=8' } + resolution: {integrity: sha512-33AmZ+xjZhg2JMCe+vDf6a9mzWukE7l+wAtesjE7KyteqqKjzxv7aVQeWnul1Ve26mWvEQqyPwl0OctNBfSR9w==} + engines: {node: '>=8'} import-fresh@3.3.1: - resolution: - { - integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ== - } - engines: { node: '>=6' } + resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==} + engines: {node: '>=6'} import-lazy@3.1.0: - resolution: - { - integrity: sha512-8/gvXvX2JMn0F+CDlSC4l6kOmVaLOO3XLkksI7CI3Ud95KDYJuYur2b9P/PUt/i/pDAMd/DulQsNbbbmRRsDIQ== - } - engines: { node: '>=6' } + resolution: {integrity: sha512-8/gvXvX2JMn0F+CDlSC4l6kOmVaLOO3XLkksI7CI3Ud95KDYJuYur2b9P/PUt/i/pDAMd/DulQsNbbbmRRsDIQ==} + engines: {node: '>=6'} imurmurhash@0.1.4: - resolution: - { - integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA== - } - engines: { node: '>=0.8.19' } + resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} + engines: {node: '>=0.8.19'} indent-string@2.1.0: - resolution: - { - integrity: sha512-aqwDFWSgSgfRaEwao5lg5KEcVd/2a+D1rvoG7NdilmYz0NwRk6StWpWdz/Hpk34MKPpx7s8XxUqimfcQK6gGlg== - } - engines: { node: '>=0.10.0' } + resolution: {integrity: sha512-aqwDFWSgSgfRaEwao5lg5KEcVd/2a+D1rvoG7NdilmYz0NwRk6StWpWdz/Hpk34MKPpx7s8XxUqimfcQK6gGlg==} + engines: {node: '>=0.10.0'} inflight@1.0.6: - resolution: - { - integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA== - } + resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} deprecated: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful. inherits@2.0.4: - resolution: - { - integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== - } + resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} ini@1.3.8: - resolution: - { - integrity: sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew== - } + resolution: {integrity: sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==} into-stream@3.1.0: - resolution: - { - integrity: sha512-TcdjPibTksa1NQximqep2r17ISRiNE9fwlfbg3F8ANdvP5/yrFTew86VcO//jk4QTaMlbjypPBq76HN2zaKfZQ== - } - engines: { node: '>=4' } + resolution: {integrity: sha512-TcdjPibTksa1NQximqep2r17ISRiNE9fwlfbg3F8ANdvP5/yrFTew86VcO//jk4QTaMlbjypPBq76HN2zaKfZQ==} + engines: {node: '>=4'} is-arrayish@0.2.1: - resolution: - { - integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg== - } + resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==} is-callable@1.2.7: - resolution: - { - integrity: sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA== - } - engines: { node: '>= 0.4' } + resolution: {integrity: sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==} + engines: {node: '>= 0.4'} is-core-module@2.16.1: - resolution: - { - integrity: sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w== - } - engines: { node: '>= 0.4' } + resolution: {integrity: sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==} + engines: {node: '>= 0.4'} is-cwebp-readable@3.0.0: - resolution: - { - integrity: sha512-bpELc7/Q1/U5MWHn4NdHI44R3jxk0h9ew9ljzabiRl70/UIjL/ZAqRMb52F5+eke/VC8yTiv4Ewryo1fPWidvA== - } + resolution: {integrity: sha512-bpELc7/Q1/U5MWHn4NdHI44R3jxk0h9ew9ljzabiRl70/UIjL/ZAqRMb52F5+eke/VC8yTiv4Ewryo1fPWidvA==} is-docker@3.0.0: - resolution: - { - integrity: sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ== - } - engines: { node: ^12.20.0 || ^14.13.1 || >=16.0.0 } + resolution: {integrity: sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} hasBin: true is-extglob@2.1.1: - resolution: - { - integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ== - } - engines: { node: '>=0.10.0' } + resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} + engines: {node: '>=0.10.0'} is-finite@1.1.0: - resolution: - { - integrity: sha512-cdyMtqX/BOqqNBBiKlIVkytNHm49MtMlYyn1zxzvJKWmFMlGzm+ry5BBfYyeY9YmNKbRSo/o7OX9w9ale0wg3w== - } - engines: { node: '>=0.10.0' } + resolution: {integrity: sha512-cdyMtqX/BOqqNBBiKlIVkytNHm49MtMlYyn1zxzvJKWmFMlGzm+ry5BBfYyeY9YmNKbRSo/o7OX9w9ale0wg3w==} + engines: {node: '>=0.10.0'} is-fullwidth-code-point@3.0.0: - resolution: - { - integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== - } - engines: { node: '>=8' } + resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} + engines: {node: '>=8'} is-gif@3.0.0: - resolution: - { - integrity: sha512-IqJ/jlbw5WJSNfwQ/lHEDXF8rxhRgF6ythk2oiEvhpG29F704eX9NO6TvPfMiq9DrbwgcEDnETYNcZDPewQoVw== - } - engines: { node: '>=6' } + resolution: {integrity: sha512-IqJ/jlbw5WJSNfwQ/lHEDXF8rxhRgF6ythk2oiEvhpG29F704eX9NO6TvPfMiq9DrbwgcEDnETYNcZDPewQoVw==} + engines: {node: '>=6'} is-glob@4.0.3: - resolution: - { - integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg== - } - engines: { node: '>=0.10.0' } + resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} + engines: {node: '>=0.10.0'} is-in-ssh@1.0.0: - resolution: - { - integrity: sha512-jYa6Q9rH90kR1vKB6NM7qqd1mge3Fx4Dhw5TVlK1MUBqhEOuCagrEHMevNuCcbECmXZ0ThXkRm+Ymr51HwEPAw== - } - engines: { node: '>=20' } + resolution: {integrity: sha512-jYa6Q9rH90kR1vKB6NM7qqd1mge3Fx4Dhw5TVlK1MUBqhEOuCagrEHMevNuCcbECmXZ0ThXkRm+Ymr51HwEPAw==} + engines: {node: '>=20'} is-inside-container@1.0.0: - resolution: - { - integrity: sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA== - } - engines: { node: '>=14.16' } + resolution: {integrity: sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==} + engines: {node: '>=14.16'} hasBin: true is-jpg@2.0.0: - resolution: - { - integrity: sha512-ODlO0ruzhkzD3sdynIainVP5eoOFNN85rxA1+cwwnPe4dKyX0r5+hxNO5XpCrxlHcmb9vkOit9mhRD2JVuimHg== - } - engines: { node: '>=6' } + resolution: {integrity: sha512-ODlO0ruzhkzD3sdynIainVP5eoOFNN85rxA1+cwwnPe4dKyX0r5+hxNO5XpCrxlHcmb9vkOit9mhRD2JVuimHg==} + engines: {node: '>=6'} is-natural-number@4.0.1: - resolution: - { - integrity: sha512-Y4LTamMe0DDQIIAlaer9eKebAlDSV6huy+TWhJVPlzZh2o4tRP5SQWFlLn5N0To4mDD22/qdOq+veo1cSISLgQ== - } + resolution: {integrity: sha512-Y4LTamMe0DDQIIAlaer9eKebAlDSV6huy+TWhJVPlzZh2o4tRP5SQWFlLn5N0To4mDD22/qdOq+veo1cSISLgQ==} is-number@7.0.0: - resolution: - { - integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== - } - engines: { node: '>=0.12.0' } + resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} + engines: {node: '>=0.12.0'} is-object@1.0.2: - resolution: - { - integrity: sha512-2rRIahhZr2UWb45fIOuvZGpFtz0TyOZLf32KxBbSoUCeZR495zCKlWUKKUByk3geS2eAs7ZAABt0Y/Rx0GiQGA== - } + resolution: {integrity: sha512-2rRIahhZr2UWb45fIOuvZGpFtz0TyOZLf32KxBbSoUCeZR495zCKlWUKKUByk3geS2eAs7ZAABt0Y/Rx0GiQGA==} is-plain-obj@1.1.0: - resolution: - { - integrity: sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg== - } - engines: { node: '>=0.10.0' } + resolution: {integrity: sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg==} + engines: {node: '>=0.10.0'} is-png@2.0.0: - resolution: - { - integrity: sha512-4KPGizaVGj2LK7xwJIz8o5B2ubu1D/vcQsgOGFEDlpcvgZHto4gBnyd0ig7Ws+67ixmwKoNmu0hYnpo6AaKb5g== - } - engines: { node: '>=8' } + resolution: {integrity: sha512-4KPGizaVGj2LK7xwJIz8o5B2ubu1D/vcQsgOGFEDlpcvgZHto4gBnyd0ig7Ws+67ixmwKoNmu0hYnpo6AaKb5g==} + engines: {node: '>=8'} is-retry-allowed@1.2.0: - resolution: - { - integrity: sha512-RUbUeKwvm3XG2VYamhJL1xFktgjvPzL0Hq8C+6yrWIswDy3BIXGqCxhxkc30N9jqK311gVU137K8Ei55/zVJRg== - } - engines: { node: '>=0.10.0' } + resolution: {integrity: sha512-RUbUeKwvm3XG2VYamhJL1xFktgjvPzL0Hq8C+6yrWIswDy3BIXGqCxhxkc30N9jqK311gVU137K8Ei55/zVJRg==} + engines: {node: '>=0.10.0'} is-stream@1.1.0: - resolution: - { - integrity: sha512-uQPm8kcs47jx38atAcWTVxyltQYoPT68y9aWYdV6yWXSyW8mzSat0TL6CiWdZeCdF3KrAvpVtnHbTv4RN+rqdQ== - } - engines: { node: '>=0.10.0' } + resolution: {integrity: sha512-uQPm8kcs47jx38atAcWTVxyltQYoPT68y9aWYdV6yWXSyW8mzSat0TL6CiWdZeCdF3KrAvpVtnHbTv4RN+rqdQ==} + engines: {node: '>=0.10.0'} is-stream@2.0.1: - resolution: - { - integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg== - } - engines: { node: '>=8' } + resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==} + engines: {node: '>=8'} is-svg@4.4.0: - resolution: - { - integrity: sha512-v+AgVwiK5DsGtT9ng+m4mClp6zDAmwrW8nZi6Gg15qzvBnRWWdfWA1TGaXyCDnWq5g5asofIgMVl3PjKxvk1ug== - } - engines: { node: '>=6' } + resolution: {integrity: sha512-v+AgVwiK5DsGtT9ng+m4mClp6zDAmwrW8nZi6Gg15qzvBnRWWdfWA1TGaXyCDnWq5g5asofIgMVl3PjKxvk1ug==} + engines: {node: '>=6'} is-typed-array@1.1.15: - resolution: - { - integrity: sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ== - } - engines: { node: '>= 0.4' } + resolution: {integrity: sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==} + engines: {node: '>= 0.4'} is-utf8@0.2.1: - resolution: - { - integrity: sha512-rMYPYvCzsXywIsldgLaSoPlw5PfoB/ssr7hY4pLfcodrA5M/eArza1a9VmTiNIBNMjOGr1Ow9mTyU2o69U6U9Q== - } + resolution: {integrity: sha512-rMYPYvCzsXywIsldgLaSoPlw5PfoB/ssr7hY4pLfcodrA5M/eArza1a9VmTiNIBNMjOGr1Ow9mTyU2o69U6U9Q==} is-wsl@3.1.1: - resolution: - { - integrity: sha512-e6rvdUCiQCAuumZslxRJWR/Doq4VpPR82kqclvcS0efgt430SlGIk05vdCN58+VrzgtIcfNODjozVielycD4Sw== - } - engines: { node: '>=16' } + resolution: {integrity: sha512-e6rvdUCiQCAuumZslxRJWR/Doq4VpPR82kqclvcS0efgt430SlGIk05vdCN58+VrzgtIcfNODjozVielycD4Sw==} + engines: {node: '>=16'} isarray@1.0.0: - resolution: - { - integrity: sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ== - } + resolution: {integrity: sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==} isarray@2.0.5: - resolution: - { - integrity: sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw== - } + resolution: {integrity: sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==} isexe@2.0.0: - resolution: - { - integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw== - } + resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} isurl@1.0.0: - resolution: - { - integrity: sha512-1P/yWsxPlDtn7QeRD+ULKQPaIaN6yF368GZ2vDfv0AL0NwpStafjWCDDdn0k8wgFMWpVAqG7oJhxHnlud42i9w== - } - engines: { node: '>= 4' } + resolution: {integrity: sha512-1P/yWsxPlDtn7QeRD+ULKQPaIaN6yF368GZ2vDfv0AL0NwpStafjWCDDdn0k8wgFMWpVAqG7oJhxHnlud42i9w==} + engines: {node: '>= 4'} javascript-natural-sort@0.7.1: - resolution: - { - integrity: sha512-nO6jcEfZWQXDhOiBtG2KvKyEptz7RVbpGP4vTD2hLBdmNQSsCiicO2Ioinv6UI4y9ukqnBpy+XZ9H6uLNgJTlw== - } + resolution: {integrity: sha512-nO6jcEfZWQXDhOiBtG2KvKyEptz7RVbpGP4vTD2hLBdmNQSsCiicO2Ioinv6UI4y9ukqnBpy+XZ9H6uLNgJTlw==} jpegtran-bin@5.0.2: - resolution: - { - integrity: sha512-4FSmgIcr8d5+V6T1+dHbPZjaFH0ogVyP4UVsE+zri7S9YLO4qAT2our4IN3sW3STVgNTbqPermdIgt2XuAJ4EA== - } - engines: { node: '>=10' } + resolution: {integrity: sha512-4FSmgIcr8d5+V6T1+dHbPZjaFH0ogVyP4UVsE+zri7S9YLO4qAT2our4IN3sW3STVgNTbqPermdIgt2XuAJ4EA==} + engines: {node: '>=10'} hasBin: true jpegtran-bin@6.0.1: - resolution: - { - integrity: sha512-WohhhHhqe22de7PU8hXs6Sr5d4BAvkrfA93NR5tGlHyPnFLgvEW/bH+q7fv65JgoiQDsd7SBwwQ/OGRBivU3Mw== - } - engines: { node: ^12.20.0 || ^14.13.1 || >=16.0.0 } + resolution: {integrity: sha512-WohhhHhqe22de7PU8hXs6Sr5d4BAvkrfA93NR5tGlHyPnFLgvEW/bH+q7fv65JgoiQDsd7SBwwQ/OGRBivU3Mw==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} hasBin: true js-tokens@4.0.0: - resolution: - { - integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== - } + resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} jsesc@3.1.0: - resolution: - { - integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA== - } - engines: { node: '>=6' } + resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==} + engines: {node: '>=6'} hasBin: true json-buffer@3.0.0: - resolution: - { - integrity: sha512-CuUqjv0FUZIdXkHPI8MezCnFCdaTAacej1TZYulLoAg1h/PhwkdXFN4V/gzY4g+fMBCOV2xF+rp7t2XD2ns/NQ== - } + resolution: {integrity: sha512-CuUqjv0FUZIdXkHPI8MezCnFCdaTAacej1TZYulLoAg1h/PhwkdXFN4V/gzY4g+fMBCOV2xF+rp7t2XD2ns/NQ==} json-buffer@3.0.1: - resolution: - { - integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ== - } + resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} json-parse-even-better-errors@2.3.1: - resolution: - { - integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w== - } + resolution: {integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==} json-schema-traverse@0.4.1: - resolution: - { - integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== - } + resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} json-stable-stringify-without-jsonify@1.0.1: - resolution: - { - integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw== - } + resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} json5@2.2.3: - resolution: - { - integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg== - } - engines: { node: '>=6' } + resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} + engines: {node: '>=6'} hasBin: true jsonfile@6.2.1: - resolution: - { - integrity: sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q== - } + resolution: {integrity: sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q==} junk@3.1.0: - resolution: - { - integrity: sha512-pBxcB3LFc8QVgdggvZWyeys+hnrNWg4OcZIU/1X59k5jQdLBlCsYGRQaz234SqoRLTCgMH00fY0xRJH+F9METQ== - } - engines: { node: '>=8' } + resolution: {integrity: sha512-pBxcB3LFc8QVgdggvZWyeys+hnrNWg4OcZIU/1X59k5jQdLBlCsYGRQaz234SqoRLTCgMH00fY0xRJH+F9METQ==} + engines: {node: '>=8'} jwt-decode@4.0.0: - resolution: - { - integrity: sha512-+KJGIyHgkGuIq3IEBNftfhW/LfWhXUIY6OmyVWjliu5KH1y0fw7VQ8YndE2O4qZdMSd9SqbnC8GOcZEy0Om7sA== - } - engines: { node: '>=18' } + resolution: {integrity: sha512-+KJGIyHgkGuIq3IEBNftfhW/LfWhXUIY6OmyVWjliu5KH1y0fw7VQ8YndE2O4qZdMSd9SqbnC8GOcZEy0Om7sA==} + engines: {node: '>=18'} keyv@3.0.0: - resolution: - { - integrity: sha512-eguHnq22OE3uVoSYG0LVWNP+4ppamWr9+zWBe1bsNcovIMy6huUJFPgy4mGwCd/rnl3vOLGW1MTlu4c57CT1xA== - } + resolution: {integrity: sha512-eguHnq22OE3uVoSYG0LVWNP+4ppamWr9+zWBe1bsNcovIMy6huUJFPgy4mGwCd/rnl3vOLGW1MTlu4c57CT1xA==} keyv@4.5.4: - resolution: - { - integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw== - } + resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} kolorist@1.8.0: - resolution: - { - integrity: sha512-Y+60/zizpJ3HRH8DCss+q95yr6145JXZo46OTpFvDZWLfRCE4qChOyk1b26nMaNpfHHgxagk9dXT5OP0Tfe+dQ== - } + resolution: {integrity: sha512-Y+60/zizpJ3HRH8DCss+q95yr6145JXZo46OTpFvDZWLfRCE4qChOyk1b26nMaNpfHHgxagk9dXT5OP0Tfe+dQ==} levn@0.4.1: - resolution: - { - integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ== - } - engines: { node: '>= 0.8.0' } + resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} + engines: {node: '>= 0.8.0'} lightningcss-android-arm64@1.32.0: - resolution: - { - integrity: sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg== - } - engines: { node: '>= 12.0.0' } + resolution: {integrity: sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==} + engines: {node: '>= 12.0.0'} cpu: [arm64] os: [android] lightningcss-darwin-arm64@1.32.0: - resolution: - { - integrity: sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ== - } - engines: { node: '>= 12.0.0' } + resolution: {integrity: sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==} + engines: {node: '>= 12.0.0'} cpu: [arm64] os: [darwin] lightningcss-darwin-x64@1.32.0: - resolution: - { - integrity: sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w== - } - engines: { node: '>= 12.0.0' } + resolution: {integrity: sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==} + engines: {node: '>= 12.0.0'} cpu: [x64] os: [darwin] lightningcss-freebsd-x64@1.32.0: - resolution: - { - integrity: sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig== - } - engines: { node: '>= 12.0.0' } + resolution: {integrity: sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==} + engines: {node: '>= 12.0.0'} cpu: [x64] os: [freebsd] lightningcss-linux-arm-gnueabihf@1.32.0: - resolution: - { - integrity: sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw== - } - engines: { node: '>= 12.0.0' } + resolution: {integrity: sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==} + engines: {node: '>= 12.0.0'} cpu: [arm] os: [linux] lightningcss-linux-arm64-gnu@1.32.0: - resolution: - { - integrity: sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ== - } - engines: { node: '>= 12.0.0' } + resolution: {integrity: sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==} + engines: {node: '>= 12.0.0'} cpu: [arm64] os: [linux] libc: [glibc] lightningcss-linux-arm64-musl@1.32.0: - resolution: - { - integrity: sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg== - } - engines: { node: '>= 12.0.0' } + resolution: {integrity: sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==} + engines: {node: '>= 12.0.0'} cpu: [arm64] os: [linux] libc: [musl] lightningcss-linux-x64-gnu@1.32.0: - resolution: - { - integrity: sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA== - } - engines: { node: '>= 12.0.0' } + resolution: {integrity: sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==} + engines: {node: '>= 12.0.0'} cpu: [x64] os: [linux] libc: [glibc] lightningcss-linux-x64-musl@1.32.0: - resolution: - { - integrity: sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg== - } - engines: { node: '>= 12.0.0' } + resolution: {integrity: sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==} + engines: {node: '>= 12.0.0'} cpu: [x64] os: [linux] libc: [musl] lightningcss-win32-arm64-msvc@1.32.0: - resolution: - { - integrity: sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw== - } - engines: { node: '>= 12.0.0' } + resolution: {integrity: sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==} + engines: {node: '>= 12.0.0'} cpu: [arm64] os: [win32] lightningcss-win32-x64-msvc@1.32.0: - resolution: - { - integrity: sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q== - } - engines: { node: '>= 12.0.0' } + resolution: {integrity: sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==} + engines: {node: '>= 12.0.0'} cpu: [x64] os: [win32] lightningcss@1.32.0: - resolution: - { - integrity: sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ== - } - engines: { node: '>= 12.0.0' } + resolution: {integrity: sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==} + engines: {node: '>= 12.0.0'} lines-and-columns@1.2.4: - resolution: - { - integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg== - } + resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} load-json-file@1.1.0: - resolution: - { - integrity: sha512-cy7ZdNRXdablkXYNI049pthVeXFurRyb9+hA/dZzerZ0pGTx42z+y+ssxBaVV2l70t1muq5IdKhn4UtcoGUY9A== - } - engines: { node: '>=0.10.0' } + resolution: {integrity: sha512-cy7ZdNRXdablkXYNI049pthVeXFurRyb9+hA/dZzerZ0pGTx42z+y+ssxBaVV2l70t1muq5IdKhn4UtcoGUY9A==} + engines: {node: '>=0.10.0'} locate-path@6.0.0: - resolution: - { - integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw== - } - engines: { node: '>=10' } + resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} + engines: {node: '>=10'} lodash-es@4.18.1: - resolution: - { - integrity: sha512-J8xewKD/Gk22OZbhpOVSwcs60zhd95ESDwezOFuA3/099925PdHJ7OFHNTGtajL3AlZkykD32HykiMo+BIBI8A== - } + resolution: {integrity: sha512-J8xewKD/Gk22OZbhpOVSwcs60zhd95ESDwezOFuA3/099925PdHJ7OFHNTGtajL3AlZkykD32HykiMo+BIBI8A==} logalot@2.1.0: - resolution: - { - integrity: sha512-Ah4CgdSRfeCJagxQhcVNMi9BfGYyEKLa6d7OA6xSbld/Hg3Cf2QiOa1mDpmG7Ve8LOH6DN3mdttzjQAvWTyVkw== - } - engines: { node: '>=0.10.0' } + resolution: {integrity: sha512-Ah4CgdSRfeCJagxQhcVNMi9BfGYyEKLa6d7OA6xSbld/Hg3Cf2QiOa1mDpmG7Ve8LOH6DN3mdttzjQAvWTyVkw==} + engines: {node: '>=0.10.0'} longest@1.0.1: - resolution: - { - integrity: sha512-k+yt5n3l48JU4k8ftnKG6V7u32wyH2NfKzeMto9F/QRE0amxy/LayxwlvjjkZEIzqR+19IrtFO8p5kB9QaYUFg== - } - engines: { node: '>=0.10.0' } + resolution: {integrity: sha512-k+yt5n3l48JU4k8ftnKG6V7u32wyH2NfKzeMto9F/QRE0amxy/LayxwlvjjkZEIzqR+19IrtFO8p5kB9QaYUFg==} + engines: {node: '>=0.10.0'} loose-envify@1.4.0: - resolution: - { - integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q== - } + resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} hasBin: true loud-rejection@1.6.0: - resolution: - { - integrity: sha512-RPNliZOFkqFumDhvYqOaNY4Uz9oJM2K9tC6JWsJJsNdhuONW4LQHRBpb0qf4pJApVffI5N39SwzWZJuEhfd7eQ== - } - engines: { node: '>=0.10.0' } + resolution: {integrity: sha512-RPNliZOFkqFumDhvYqOaNY4Uz9oJM2K9tC6JWsJJsNdhuONW4LQHRBpb0qf4pJApVffI5N39SwzWZJuEhfd7eQ==} + engines: {node: '>=0.10.0'} lowercase-keys@1.0.0: - resolution: - { - integrity: sha512-RPlX0+PHuvxVDZ7xX+EBVAp4RsVxP/TdDSN2mJYdiq1Lc4Hz7EUSjUI7RZrKKlmrIzVhf6Jo2stj7++gVarS0A== - } - engines: { node: '>=0.10.0' } + resolution: {integrity: sha512-RPlX0+PHuvxVDZ7xX+EBVAp4RsVxP/TdDSN2mJYdiq1Lc4Hz7EUSjUI7RZrKKlmrIzVhf6Jo2stj7++gVarS0A==} + engines: {node: '>=0.10.0'} lowercase-keys@1.0.1: - resolution: - { - integrity: sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA== - } - engines: { node: '>=0.10.0' } + resolution: {integrity: sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA==} + engines: {node: '>=0.10.0'} lpad-align@1.1.2: - resolution: - { - integrity: sha512-MMIcFmmR9zlGZtBcFOows6c2COMekHCIFJz3ew/rRpKZ1wR4mXDPzvcVqLarux8M33X4TPSq2Jdw8WJj0q0KbQ== - } - engines: { node: '>=0.10.0' } + resolution: {integrity: sha512-MMIcFmmR9zlGZtBcFOows6c2COMekHCIFJz3ew/rRpKZ1wR4mXDPzvcVqLarux8M33X4TPSq2Jdw8WJj0q0KbQ==} + engines: {node: '>=0.10.0'} hasBin: true lru-cache@4.1.5: - resolution: - { - integrity: sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g== - } + resolution: {integrity: sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==} lru-cache@5.1.1: - resolution: - { - integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w== - } + resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} magic-string@0.30.21: - resolution: - { - integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ== - } + resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} make-dir@1.3.0: - resolution: - { - integrity: sha512-2w31R7SJtieJJnQtGc7RVL2StM2vGYVfqUOvUDxH6bC6aJTxPxTF0GnIgCyu7tjockiUWAYQRbxa7vKn34s5sQ== - } - engines: { node: '>=4' } + resolution: {integrity: sha512-2w31R7SJtieJJnQtGc7RVL2StM2vGYVfqUOvUDxH6bC6aJTxPxTF0GnIgCyu7tjockiUWAYQRbxa7vKn34s5sQ==} + engines: {node: '>=4'} make-dir@3.1.0: - resolution: - { - integrity: sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw== - } - engines: { node: '>=8' } + resolution: {integrity: sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==} + engines: {node: '>=8'} map-obj@1.0.1: - resolution: - { - integrity: sha512-7N/q3lyZ+LVCp7PzuxrJr4KMbBE2hW7BT7YNia330OFxIf4d3r5zVpicP2650l7CPN6RM9zOJRl3NGpqSiw3Eg== - } - engines: { node: '>=0.10.0' } + resolution: {integrity: sha512-7N/q3lyZ+LVCp7PzuxrJr4KMbBE2hW7BT7YNia330OFxIf4d3r5zVpicP2650l7CPN6RM9zOJRl3NGpqSiw3Eg==} + engines: {node: '>=0.10.0'} math-intrinsics@1.1.0: - resolution: - { - integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g== - } - engines: { node: '>= 0.4' } + resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} + engines: {node: '>= 0.4'} mdn-data@2.0.14: - resolution: - { - integrity: sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow== - } + resolution: {integrity: sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow==} memoize-one@5.2.1: - resolution: - { - integrity: sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q== - } + resolution: {integrity: sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q==} meow@3.7.0: - resolution: - { - integrity: sha512-TNdwZs0skRlpPpCUK25StC4VH+tP5GgeY1HQOOGP+lQ2xtdkN2VtT/5tiX9k3IWpkBPV9b3LsAWXn4GGi/PrSA== - } - engines: { node: '>=0.10.0' } + resolution: {integrity: sha512-TNdwZs0skRlpPpCUK25StC4VH+tP5GgeY1HQOOGP+lQ2xtdkN2VtT/5tiX9k3IWpkBPV9b3LsAWXn4GGi/PrSA==} + engines: {node: '>=0.10.0'} merge-stream@2.0.0: - resolution: - { - integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w== - } + resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==} merge2@1.4.1: - resolution: - { - integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg== - } - engines: { node: '>= 8' } + resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} + engines: {node: '>= 8'} micromatch@4.0.8: - resolution: - { - integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA== - } - engines: { node: '>=8.6' } + resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} + engines: {node: '>=8.6'} mime-db@1.54.0: - resolution: - { - integrity: sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ== - } - engines: { node: '>= 0.6' } + resolution: {integrity: sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==} + engines: {node: '>= 0.6'} mime-types@3.0.2: - resolution: - { - integrity: sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A== - } - engines: { node: '>=18' } + resolution: {integrity: sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==} + engines: {node: '>=18'} mimic-fn@2.1.0: - resolution: - { - integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== - } - engines: { node: '>=6' } + resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==} + engines: {node: '>=6'} mimic-response@1.0.1: - resolution: - { - integrity: sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ== - } - engines: { node: '>=4' } + resolution: {integrity: sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==} + engines: {node: '>=4'} minimatch@10.2.5: - resolution: - { - integrity: sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg== - } - engines: { node: 18 || 20 || >=22 } + resolution: {integrity: sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==} + engines: {node: 18 || 20 || >=22} minimatch@3.1.5: - resolution: - { - integrity: sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w== - } + resolution: {integrity: sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==} minimatch@9.0.9: - resolution: - { - integrity: sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg== - } - engines: { node: '>=16 || 14 >=14.17' } + resolution: {integrity: sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==} + engines: {node: '>=16 || 14 >=14.17'} minimist@1.2.8: - resolution: - { - integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA== - } + resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} mozjpeg@7.1.1: - resolution: - { - integrity: sha512-iIDxWvzhWvLC9mcRJ1uSkiKaj4drF58oCqK2bITm5c2Jt6cJ8qQjSSru2PCaysG+hLIinryj8mgz5ZJzOYTv1A== - } - engines: { node: '>=10' } + resolution: {integrity: sha512-iIDxWvzhWvLC9mcRJ1uSkiKaj4drF58oCqK2bITm5c2Jt6cJ8qQjSSru2PCaysG+hLIinryj8mgz5ZJzOYTv1A==} + engines: {node: '>=10'} hasBin: true ms@2.1.3: - resolution: - { - integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== - } + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} - nanoid@3.3.11: - resolution: - { - integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w== - } - engines: { node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1 } + nanoid@3.3.12: + resolution: {integrity: sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} hasBin: true natural-compare@1.4.0: - resolution: - { - integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw== - } + resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} nice-try@1.0.5: - resolution: - { - integrity: sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ== - } + resolution: {integrity: sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==} node-html-parser@6.1.13: - resolution: - { - integrity: sha512-qIsTMOY4C/dAa5Q5vsobRpOOvPfC4pB61UVW2uSwZNUp0QU/jCekTal1vMmbO0DgdHeLUJpv/ARmDqErVxA3Sg== - } + resolution: {integrity: sha512-qIsTMOY4C/dAa5Q5vsobRpOOvPfC4pB61UVW2uSwZNUp0QU/jCekTal1vMmbO0DgdHeLUJpv/ARmDqErVxA3Sg==} node-releases@2.0.38: - resolution: - { - integrity: sha512-3qT/88Y3FbH/Kx4szpQQ4HzUbVrHPKTLVpVocKiLfoYvw9XSGOX2FmD2d6DrXbVYyAQTF2HeF6My8jmzx7/CRw== - } + resolution: {integrity: sha512-3qT/88Y3FbH/Kx4szpQQ4HzUbVrHPKTLVpVocKiLfoYvw9XSGOX2FmD2d6DrXbVYyAQTF2HeF6My8jmzx7/CRw==} normalize-package-data@2.5.0: - resolution: - { - integrity: sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA== - } + resolution: {integrity: sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==} normalize-url@2.0.1: - resolution: - { - integrity: sha512-D6MUW4K/VzoJ4rJ01JFKxDrtY1v9wrgzCX5f2qj/lzH1m/lW6MhUZFKerVsnyjOhOsYzI9Kqqak+10l4LvLpMw== - } - engines: { node: '>=4' } + resolution: {integrity: sha512-D6MUW4K/VzoJ4rJ01JFKxDrtY1v9wrgzCX5f2qj/lzH1m/lW6MhUZFKerVsnyjOhOsYzI9Kqqak+10l4LvLpMw==} + engines: {node: '>=4'} npm-conf@1.1.3: - resolution: - { - integrity: sha512-Yic4bZHJOt9RCFbRP3GgpqhScOY4HH3V2P8yBj6CeYq118Qr+BLXqT2JvpJ00mryLESpgOxf5XlFv4ZjXxLScw== - } - engines: { node: '>=4' } + resolution: {integrity: sha512-Yic4bZHJOt9RCFbRP3GgpqhScOY4HH3V2P8yBj6CeYq118Qr+BLXqT2JvpJ00mryLESpgOxf5XlFv4ZjXxLScw==} + engines: {node: '>=4'} npm-run-path@2.0.2: - resolution: - { - integrity: sha512-lJxZYlT4DW/bRUtFh1MQIWqmLwQfAxnqWG4HhEdjMlkrJYnJn0Jrr2u3mgxqaWsdiBc76TYkTG/mhrnYTuzfHw== - } - engines: { node: '>=4' } + resolution: {integrity: sha512-lJxZYlT4DW/bRUtFh1MQIWqmLwQfAxnqWG4HhEdjMlkrJYnJn0Jrr2u3mgxqaWsdiBc76TYkTG/mhrnYTuzfHw==} + engines: {node: '>=4'} npm-run-path@4.0.1: - resolution: - { - integrity: sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw== - } - engines: { node: '>=8' } + resolution: {integrity: sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==} + engines: {node: '>=8'} nth-check@2.1.1: - resolution: - { - integrity: sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w== - } + resolution: {integrity: sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==} object-assign@4.1.1: - resolution: - { - integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg== - } - engines: { node: '>=0.10.0' } + resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} + engines: {node: '>=0.10.0'} once@1.4.0: - resolution: - { - integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w== - } + resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} onetime@5.1.2: - resolution: - { - integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg== - } - engines: { node: '>=6' } + resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==} + engines: {node: '>=6'} open@11.0.0: - resolution: - { - integrity: sha512-smsWv2LzFjP03xmvFoJ331ss6h+jixfA4UUV/Bsiyuu4YJPfN+FIQGOIiv4w9/+MoHkfkJ22UIaQWRVFRfH6Vw== - } - engines: { node: '>=20' } + resolution: {integrity: sha512-smsWv2LzFjP03xmvFoJ331ss6h+jixfA4UUV/Bsiyuu4YJPfN+FIQGOIiv4w9/+MoHkfkJ22UIaQWRVFRfH6Vw==} + engines: {node: '>=20'} optionator@0.9.4: - resolution: - { - integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g== - } - engines: { node: '>= 0.8.0' } + resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} + engines: {node: '>= 0.8.0'} optipng-bin@7.0.1: - resolution: - { - integrity: sha512-W99mpdW7Nt2PpFiaO+74pkht7KEqkXkeRomdWXfEz3SALZ6hns81y/pm1dsGZ6ItUIfchiNIP6ORDr1zETU1jA== - } - engines: { node: '>=10' } + resolution: {integrity: sha512-W99mpdW7Nt2PpFiaO+74pkht7KEqkXkeRomdWXfEz3SALZ6hns81y/pm1dsGZ6ItUIfchiNIP6ORDr1zETU1jA==} + engines: {node: '>=10'} hasBin: true os-filter-obj@2.0.0: - resolution: - { - integrity: sha512-uksVLsqG3pVdzzPvmAHpBK0wKxYItuzZr7SziusRPoz67tGV8rL1szZ6IdeUrbqLjGDwApBtN29eEE3IqGHOjg== - } - engines: { node: '>=4' } + resolution: {integrity: sha512-uksVLsqG3pVdzzPvmAHpBK0wKxYItuzZr7SziusRPoz67tGV8rL1szZ6IdeUrbqLjGDwApBtN29eEE3IqGHOjg==} + engines: {node: '>=4'} ow@0.17.0: - resolution: - { - integrity: sha512-i3keDzDQP5lWIe4oODyDFey1qVrq2hXKTuTH2VpqwpYtzPiKZt2ziRI4NBQmgW40AnV5Euz17OyWweCb+bNEQA== - } - engines: { node: '>=10' } + resolution: {integrity: sha512-i3keDzDQP5lWIe4oODyDFey1qVrq2hXKTuTH2VpqwpYtzPiKZt2ziRI4NBQmgW40AnV5Euz17OyWweCb+bNEQA==} + engines: {node: '>=10'} p-cancelable@0.3.0: - resolution: - { - integrity: sha512-RVbZPLso8+jFeq1MfNvgXtCRED2raz/dKpacfTNxsx6pLEpEomM7gah6VeHSYV3+vo0OAi4MkArtQcWWXuQoyw== - } - engines: { node: '>=4' } + resolution: {integrity: sha512-RVbZPLso8+jFeq1MfNvgXtCRED2raz/dKpacfTNxsx6pLEpEomM7gah6VeHSYV3+vo0OAi4MkArtQcWWXuQoyw==} + engines: {node: '>=4'} p-cancelable@0.4.1: - resolution: - { - integrity: sha512-HNa1A8LvB1kie7cERyy21VNeHb2CWJJYqyyC2o3klWFfMGlFmWv2Z7sFgZH8ZiaYL95ydToKTFVXgMV/Os0bBQ== - } - engines: { node: '>=4' } + resolution: {integrity: sha512-HNa1A8LvB1kie7cERyy21VNeHb2CWJJYqyyC2o3klWFfMGlFmWv2Z7sFgZH8ZiaYL95ydToKTFVXgMV/Os0bBQ==} + engines: {node: '>=4'} p-event@1.3.0: - resolution: - { - integrity: sha512-hV1zbA7gwqPVFcapfeATaNjQ3J0NuzorHPyG8GPL9g/Y/TplWVBVoCKCXL6Ej2zscrCEv195QNWJXuBH6XZuzA== - } - engines: { node: '>=4' } + resolution: {integrity: sha512-hV1zbA7gwqPVFcapfeATaNjQ3J0NuzorHPyG8GPL9g/Y/TplWVBVoCKCXL6Ej2zscrCEv195QNWJXuBH6XZuzA==} + engines: {node: '>=4'} p-event@2.3.1: - resolution: - { - integrity: sha512-NQCqOFhbpVTMX4qMe8PF8lbGtzZ+LCiN7pcNrb/413Na7+TRoe1xkKUzuWa/YEJdGQ0FvKtj35EEbDoVPO2kbA== - } - engines: { node: '>=6' } + resolution: {integrity: sha512-NQCqOFhbpVTMX4qMe8PF8lbGtzZ+LCiN7pcNrb/413Na7+TRoe1xkKUzuWa/YEJdGQ0FvKtj35EEbDoVPO2kbA==} + engines: {node: '>=6'} p-finally@1.0.0: - resolution: - { - integrity: sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow== - } - engines: { node: '>=4' } + resolution: {integrity: sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow==} + engines: {node: '>=4'} p-is-promise@1.1.0: - resolution: - { - integrity: sha512-zL7VE4JVS2IFSkR2GQKDSPEVxkoH43/p7oEnwpdCndKYJO0HVeRB7fA8TJwuLOTBREtK0ea8eHaxdwcpob5dmg== - } - engines: { node: '>=4' } + resolution: {integrity: sha512-zL7VE4JVS2IFSkR2GQKDSPEVxkoH43/p7oEnwpdCndKYJO0HVeRB7fA8TJwuLOTBREtK0ea8eHaxdwcpob5dmg==} + engines: {node: '>=4'} p-limit@3.1.0: - resolution: - { - integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ== - } - engines: { node: '>=10' } + resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} + engines: {node: '>=10'} p-locate@5.0.0: - resolution: - { - integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw== - } - engines: { node: '>=10' } + resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} + engines: {node: '>=10'} p-map-series@1.0.0: - resolution: - { - integrity: sha512-4k9LlvY6Bo/1FcIdV33wqZQES0Py+iKISU9Uc8p8AjWoZPnFKMpVIVD3s0EYn4jzLh1I+WeUZkJ0Yoa4Qfw3Kg== - } - engines: { node: '>=4' } + resolution: {integrity: sha512-4k9LlvY6Bo/1FcIdV33wqZQES0Py+iKISU9Uc8p8AjWoZPnFKMpVIVD3s0EYn4jzLh1I+WeUZkJ0Yoa4Qfw3Kg==} + engines: {node: '>=4'} p-pipe@3.1.0: - resolution: - { - integrity: sha512-08pj8ATpzMR0Y80x50yJHn37NF6vjrqHutASaX5LiH5npS9XPvrUmscd9MF5R4fuYRHOxQR1FfMIlF7AzwoPqw== - } - engines: { node: '>=8' } + resolution: {integrity: sha512-08pj8ATpzMR0Y80x50yJHn37NF6vjrqHutASaX5LiH5npS9XPvrUmscd9MF5R4fuYRHOxQR1FfMIlF7AzwoPqw==} + engines: {node: '>=8'} p-reduce@1.0.0: - resolution: - { - integrity: sha512-3Tx1T3oM1xO/Y8Gj0sWyE78EIJZ+t+aEmXUdvQgvGmSMri7aPTHoovbXEreWKkL5j21Er60XAWLTzKbAKYOujQ== - } - engines: { node: '>=4' } + resolution: {integrity: sha512-3Tx1T3oM1xO/Y8Gj0sWyE78EIJZ+t+aEmXUdvQgvGmSMri7aPTHoovbXEreWKkL5j21Er60XAWLTzKbAKYOujQ==} + engines: {node: '>=4'} p-timeout@1.2.1: - resolution: - { - integrity: sha512-gb0ryzr+K2qFqFv6qi3khoeqMZF/+ajxQipEF6NteZVnvz9tzdsfAVj3lYtn1gAXvH5lfLwfxEII799gt/mRIA== - } - engines: { node: '>=4' } + resolution: {integrity: sha512-gb0ryzr+K2qFqFv6qi3khoeqMZF/+ajxQipEF6NteZVnvz9tzdsfAVj3lYtn1gAXvH5lfLwfxEII799gt/mRIA==} + engines: {node: '>=4'} p-timeout@2.0.1: - resolution: - { - integrity: sha512-88em58dDVB/KzPEx1X0N3LwFfYZPyDc4B6eF38M1rk9VTZMbxXXgjugz8mmwpS9Ox4BDZ+t6t3QP5+/gazweIA== - } - engines: { node: '>=4' } + resolution: {integrity: sha512-88em58dDVB/KzPEx1X0N3LwFfYZPyDc4B6eF38M1rk9VTZMbxXXgjugz8mmwpS9Ox4BDZ+t6t3QP5+/gazweIA==} + engines: {node: '>=4'} parent-module@1.0.1: - resolution: - { - integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g== - } - engines: { node: '>=6' } + resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} + engines: {node: '>=6'} parse-imports-exports@0.2.4: - resolution: - { - integrity: sha512-4s6vd6dx1AotCx/RCI2m7t7GCh5bDRUtGNvRfHSP2wbBQdMi67pPe7mtzmgwcaQ8VKK/6IB7Glfyu3qdZJPybQ== - } + resolution: {integrity: sha512-4s6vd6dx1AotCx/RCI2m7t7GCh5bDRUtGNvRfHSP2wbBQdMi67pPe7mtzmgwcaQ8VKK/6IB7Glfyu3qdZJPybQ==} parse-json@2.2.0: - resolution: - { - integrity: sha512-QR/GGaKCkhwk1ePQNYDRKYZ3mwU9ypsKhB0XyFnLQdomyEqk3e8wpW3V5Jp88zbxK4n5ST1nqo+g9juTpownhQ== - } - engines: { node: '>=0.10.0' } + resolution: {integrity: sha512-QR/GGaKCkhwk1ePQNYDRKYZ3mwU9ypsKhB0XyFnLQdomyEqk3e8wpW3V5Jp88zbxK4n5ST1nqo+g9juTpownhQ==} + engines: {node: '>=0.10.0'} parse-json@5.2.0: - resolution: - { - integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg== - } - engines: { node: '>=8' } + resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==} + engines: {node: '>=8'} parse-statements@1.0.11: - resolution: - { - integrity: sha512-HlsyYdMBnbPQ9Jr/VgJ1YF4scnldvJpJxCVx6KgqPL4dxppsWrJHCIIxQXMJrqGnsRkNPATbeMJ8Yxu7JMsYcA== - } + resolution: {integrity: sha512-HlsyYdMBnbPQ9Jr/VgJ1YF4scnldvJpJxCVx6KgqPL4dxppsWrJHCIIxQXMJrqGnsRkNPATbeMJ8Yxu7JMsYcA==} path-exists@2.1.0: - resolution: - { - integrity: sha512-yTltuKuhtNeFJKa1PiRzfLAU5182q1y4Eb4XCJ3PBqyzEDkAZRzBrKKBct682ls9reBVHf9udYLN5Nd+K1B9BQ== - } - engines: { node: '>=0.10.0' } + resolution: {integrity: sha512-yTltuKuhtNeFJKa1PiRzfLAU5182q1y4Eb4XCJ3PBqyzEDkAZRzBrKKBct682ls9reBVHf9udYLN5Nd+K1B9BQ==} + engines: {node: '>=0.10.0'} path-exists@4.0.0: - resolution: - { - integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w== - } - engines: { node: '>=8' } + resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} + engines: {node: '>=8'} path-is-absolute@1.0.1: - resolution: - { - integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg== - } - engines: { node: '>=0.10.0' } + resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==} + engines: {node: '>=0.10.0'} path-key@2.0.1: - resolution: - { - integrity: sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw== - } - engines: { node: '>=4' } + resolution: {integrity: sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw==} + engines: {node: '>=4'} path-key@3.1.1: - resolution: - { - integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== - } - engines: { node: '>=8' } + resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} + engines: {node: '>=8'} path-parse@1.0.7: - resolution: - { - integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== - } + resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} path-type@1.1.0: - resolution: - { - integrity: sha512-S4eENJz1pkiQn9Znv33Q+deTOKmbl+jj1Fl+qiP/vYezj+S8x+J3Uo0ISrx/QoEvIlOaDWJhPaRd1flJ9HXZqg== - } - engines: { node: '>=0.10.0' } + resolution: {integrity: sha512-S4eENJz1pkiQn9Znv33Q+deTOKmbl+jj1Fl+qiP/vYezj+S8x+J3Uo0ISrx/QoEvIlOaDWJhPaRd1flJ9HXZqg==} + engines: {node: '>=0.10.0'} path-type@4.0.0: - resolution: - { - integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw== - } - engines: { node: '>=8' } + resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} + engines: {node: '>=8'} pathe@0.2.0: - resolution: - { - integrity: sha512-sTitTPYnn23esFR3RlqYBWn4c45WGeLcsKzQiUpXJAyfcWkolvlYpV8FLo7JishK946oQwMFUCHXQ9AjGPKExw== - } + resolution: {integrity: sha512-sTitTPYnn23esFR3RlqYBWn4c45WGeLcsKzQiUpXJAyfcWkolvlYpV8FLo7JishK946oQwMFUCHXQ9AjGPKExw==} pend@1.2.0: - resolution: - { - integrity: sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg== - } + resolution: {integrity: sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==} picocolors@1.1.1: - resolution: - { - integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA== - } + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} picomatch@2.3.2: - resolution: - { - integrity: sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA== - } - engines: { node: '>=8.6' } + resolution: {integrity: sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==} + engines: {node: '>=8.6'} picomatch@4.0.4: - resolution: - { - integrity: sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A== - } - engines: { node: '>=12' } + resolution: {integrity: sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==} + engines: {node: '>=12'} pify@2.3.0: - resolution: - { - integrity: sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog== - } - engines: { node: '>=0.10.0' } + resolution: {integrity: sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==} + engines: {node: '>=0.10.0'} pify@3.0.0: - resolution: - { - integrity: sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg== - } - engines: { node: '>=4' } + resolution: {integrity: sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg==} + engines: {node: '>=4'} pify@4.0.1: - resolution: - { - integrity: sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g== - } - engines: { node: '>=6' } + resolution: {integrity: sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==} + engines: {node: '>=6'} pinkie-promise@2.0.1: - resolution: - { - integrity: sha512-0Gni6D4UcLTbv9c57DfxDGdr41XfgUjqWZu492f0cIGr16zDU06BWP/RAEvOuo7CQ0CNjHaLlM59YJJFm3NWlw== - } - engines: { node: '>=0.10.0' } + resolution: {integrity: sha512-0Gni6D4UcLTbv9c57DfxDGdr41XfgUjqWZu492f0cIGr16zDU06BWP/RAEvOuo7CQ0CNjHaLlM59YJJFm3NWlw==} + engines: {node: '>=0.10.0'} pinkie@2.0.4: - resolution: - { - integrity: sha512-MnUuEycAemtSaeFSjXKW/aroV7akBbY+Sv+RkyqFjgAe73F+MR0TBWKBRDkmfWq/HiFmdavfZ1G7h4SPZXaCSg== - } - engines: { node: '>=0.10.0' } + resolution: {integrity: sha512-MnUuEycAemtSaeFSjXKW/aroV7akBbY+Sv+RkyqFjgAe73F+MR0TBWKBRDkmfWq/HiFmdavfZ1G7h4SPZXaCSg==} + engines: {node: '>=0.10.0'} pngquant-bin@6.0.1: - resolution: - { - integrity: sha512-Q3PUyolfktf+hYio6wsg3SanQzEU/v8aICg/WpzxXcuCMRb7H2Q81okfpcEztbMvw25ILjd3a87doj2N9kvbpQ== - } - engines: { node: '>=10' } + resolution: {integrity: sha512-Q3PUyolfktf+hYio6wsg3SanQzEU/v8aICg/WpzxXcuCMRb7H2Q81okfpcEztbMvw25ILjd3a87doj2N9kvbpQ==} + engines: {node: '>=10'} hasBin: true possible-typed-array-names@1.1.0: - resolution: - { - integrity: sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg== - } - engines: { node: '>= 0.4' } + resolution: {integrity: sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==} + engines: {node: '>= 0.4'} - postcss@8.5.12: - resolution: - { - integrity: sha512-W62t/Se6rA0Az3DfCL0AqJwXuKwBeYg6nOaIgzP+xZ7N5BFCI7DYi1qs6ygUYT6rvfi6t9k65UMLJC+PHZpDAA== - } - engines: { node: ^10 || ^12 || >=14 } + postcss@8.5.13: + resolution: {integrity: sha512-qif0+jGGZoLWdHey3UFHHWP0H7Gbmsk8T5VEqyYFbWqPr1XqvLGBbk/sl8V5exGmcYJklJOhOQq1pV9IcsiFag==} + engines: {node: ^10 || ^12 || >=14} powershell-utils@0.1.0: - resolution: - { - integrity: sha512-dM0jVuXJPsDN6DvRpea484tCUaMiXWjuCn++HGTqUWzGDjv5tZkEZldAJ/UMlqRYGFrD/etByo4/xOuC/snX2A== - } - engines: { node: '>=20' } + resolution: {integrity: sha512-dM0jVuXJPsDN6DvRpea484tCUaMiXWjuCn++HGTqUWzGDjv5tZkEZldAJ/UMlqRYGFrD/etByo4/xOuC/snX2A==} + engines: {node: '>=20'} preact@10.29.1: - resolution: - { - integrity: sha512-gQCLc/vWroE8lIpleXtdJhTFDogTdZG9AjMUpVkDf2iTCNwYNWA+u16dL41TqUDJO4gm2IgrcMv3uTpjd4Pwmg== - } + resolution: {integrity: sha512-gQCLc/vWroE8lIpleXtdJhTFDogTdZG9AjMUpVkDf2iTCNwYNWA+u16dL41TqUDJO4gm2IgrcMv3uTpjd4Pwmg==} prelude-ls@1.2.1: - resolution: - { - integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g== - } - engines: { node: '>= 0.8.0' } + resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} + engines: {node: '>= 0.8.0'} prepend-http@1.0.4: - resolution: - { - integrity: sha512-PhmXi5XmoyKw1Un4E+opM2KcsJInDvKyuOumcjjw3waw86ZNjHwVUOOWLc4bCzLdcKNaWBH9e99sbWzDQsVaYg== - } - engines: { node: '>=0.10.0' } + resolution: {integrity: sha512-PhmXi5XmoyKw1Un4E+opM2KcsJInDvKyuOumcjjw3waw86ZNjHwVUOOWLc4bCzLdcKNaWBH9e99sbWzDQsVaYg==} + engines: {node: '>=0.10.0'} prepend-http@2.0.0: - resolution: - { - integrity: sha512-ravE6m9Atw9Z/jjttRUZ+clIXogdghyZAuWJ3qEzjT+jI/dL1ifAqhZeC5VHzQp1MSt1+jxKkFNemj/iO7tVUA== - } - engines: { node: '>=4' } + resolution: {integrity: sha512-ravE6m9Atw9Z/jjttRUZ+clIXogdghyZAuWJ3qEzjT+jI/dL1ifAqhZeC5VHzQp1MSt1+jxKkFNemj/iO7tVUA==} + engines: {node: '>=4'} prettier@3.8.3: - resolution: - { - integrity: sha512-7igPTM53cGHMW8xWuVTydi2KO233VFiTNyF5hLJqpilHfmn8C8gPf+PS7dUT64YcXFbiMGZxS9pCSxL/Dxm/Jw== - } - engines: { node: '>=14' } + resolution: {integrity: sha512-7igPTM53cGHMW8xWuVTydi2KO233VFiTNyF5hLJqpilHfmn8C8gPf+PS7dUT64YcXFbiMGZxS9pCSxL/Dxm/Jw==} + engines: {node: '>=14'} hasBin: true process-nextick-args@2.0.1: - resolution: - { - integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag== - } + resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==} prop-types@15.8.1: - resolution: - { - integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg== - } + resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==} proto-list@1.2.4: - resolution: - { - integrity: sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA== - } + resolution: {integrity: sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==} pseudomap@1.0.2: - resolution: - { - integrity: sha512-b/YwNhb8lk1Zz2+bXXpS/LK9OisiZZ1SNsSLxN1x2OXVEhW2Ckr/7mWE5vrC1ZTiJlD9g19jWszTmJsB+oEpFQ== - } + resolution: {integrity: sha512-b/YwNhb8lk1Zz2+bXXpS/LK9OisiZZ1SNsSLxN1x2OXVEhW2Ckr/7mWE5vrC1ZTiJlD9g19jWszTmJsB+oEpFQ==} pump@3.0.4: - resolution: - { - integrity: sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA== - } + resolution: {integrity: sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA==} punycode@2.3.1: - resolution: - { - integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg== - } - engines: { node: '>=6' } + resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} + engines: {node: '>=6'} query-string@5.1.1: - resolution: - { - integrity: sha512-gjWOsm2SoGlgLEdAGt7a6slVOk9mGiXmPFMqrEhLQ68rhQuBnpfs3+EmlvqKyxnCo9/PPlF+9MtY02S1aFg+Jw== - } - engines: { node: '>=0.10.0' } + resolution: {integrity: sha512-gjWOsm2SoGlgLEdAGt7a6slVOk9mGiXmPFMqrEhLQ68rhQuBnpfs3+EmlvqKyxnCo9/PPlF+9MtY02S1aFg+Jw==} + engines: {node: '>=0.10.0'} queue-microtask@1.2.3: - resolution: - { - integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A== - } + resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} rate-limiter-flexible@5.0.5: - resolution: - { - integrity: sha512-+/dSQfo+3FYwYygUs/V2BBdwGa9nFtakDwKt4l0bnvNB53TNT++QSFewwHX9qXrZJuMe9j+TUaU21lm5ARgqdQ== - } + resolution: {integrity: sha512-+/dSQfo+3FYwYygUs/V2BBdwGa9nFtakDwKt4l0bnvNB53TNT++QSFewwHX9qXrZJuMe9j+TUaU21lm5ARgqdQ==} react-dom@19.2.5: - resolution: - { - integrity: sha512-J5bAZz+DXMMwW/wV3xzKke59Af6CHY7G4uYLN1OvBcKEsWOs4pQExj86BBKamxl/Ik5bx9whOrvBlSDfWzgSag== - } + resolution: {integrity: sha512-J5bAZz+DXMMwW/wV3xzKke59Af6CHY7G4uYLN1OvBcKEsWOs4pQExj86BBKamxl/Ik5bx9whOrvBlSDfWzgSag==} peerDependencies: react: ^19.2.5 react-icons@5.6.0: - resolution: - { - integrity: sha512-RH93p5ki6LfOiIt0UtDyNg/cee+HLVR6cHHtW3wALfo+eOHTp8RnU2kRkI6E+H19zMIs03DyxUG/GfZMOGvmiA== - } + resolution: {integrity: sha512-RH93p5ki6LfOiIt0UtDyNg/cee+HLVR6cHHtW3wALfo+eOHTp8RnU2kRkI6E+H19zMIs03DyxUG/GfZMOGvmiA==} peerDependencies: react: '*' react-is@16.13.1: - resolution: - { - integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ== - } + resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==} react-is@19.2.5: - resolution: - { - integrity: sha512-Dn0t8IQhCmeIT3wu+Apm1/YVsJXsGWi6k4sPdnBIdqMVtHtv0IGi6dcpNpNkNac0zB2uUAqNX3MHzN8c+z2rwQ== - } + resolution: {integrity: sha512-Dn0t8IQhCmeIT3wu+Apm1/YVsJXsGWi6k4sPdnBIdqMVtHtv0IGi6dcpNpNkNac0zB2uUAqNX3MHzN8c+z2rwQ==} react-router@7.14.2: - resolution: - { - integrity: sha512-yCqNne6I8IB6rVCH7XUvlBK7/QKyqypBFGv+8dj4QBFJiiRX+FG7/nkdAvGElyvVZ/HQP5N19wzteuTARXi5Gw== - } - engines: { node: '>=20.0.0' } + resolution: {integrity: sha512-yCqNne6I8IB6rVCH7XUvlBK7/QKyqypBFGv+8dj4QBFJiiRX+FG7/nkdAvGElyvVZ/HQP5N19wzteuTARXi5Gw==} + engines: {node: '>=20.0.0'} peerDependencies: react: '>=18' react-dom: '>=18' @@ -4574,147 +2758,90 @@ packages: optional: true react-toastify@11.1.0: - resolution: - { - integrity: sha512-e9h23x3phN0wbFeB6yovmWp7lobzV4CaCH0LO8nVP6H7Y+3GbcLpIzMm9dJhcp1RXbpyfvjgpfXqO80QAmn7sg== - } + resolution: {integrity: sha512-e9h23x3phN0wbFeB6yovmWp7lobzV4CaCH0LO8nVP6H7Y+3GbcLpIzMm9dJhcp1RXbpyfvjgpfXqO80QAmn7sg==} peerDependencies: react: ^18 || ^19 react-dom: ^18 || ^19 react-transition-group@4.4.5: - resolution: - { - integrity: sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g== - } + resolution: {integrity: sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==} peerDependencies: react: '>=16.6.0' react-dom: '>=16.6.0' react-virtualized-auto-sizer@1.0.26: - resolution: - { - integrity: sha512-CblNyiNVw2o+hsa5/49NH2ogGxZ+t+3aweRvNSq7TVjDIlwk7ir4lencEg5HxHeSzwNarSkNkiu0qJSOXtxm5A== - } + resolution: {integrity: sha512-CblNyiNVw2o+hsa5/49NH2ogGxZ+t+3aweRvNSq7TVjDIlwk7ir4lencEg5HxHeSzwNarSkNkiu0qJSOXtxm5A==} peerDependencies: react: ^15.3.0 || ^16.0.0-alpha || ^17.0.0 || ^18.0.0 || ^19.0.0 react-dom: ^15.3.0 || ^16.0.0-alpha || ^17.0.0 || ^18.0.0 || ^19.0.0 react-window@1.8.11: - resolution: - { - integrity: sha512-+SRbUVT2scadgFSWx+R1P754xHPEqvcfSfVX10QYg6POOz+WNgkN48pS+BtZNIMGiL1HYrSEiCkwsMS15QogEQ== - } - engines: { node: '>8.0.0' } + resolution: {integrity: sha512-+SRbUVT2scadgFSWx+R1P754xHPEqvcfSfVX10QYg6POOz+WNgkN48pS+BtZNIMGiL1HYrSEiCkwsMS15QogEQ==} + engines: {node: '>8.0.0'} peerDependencies: react: ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 react-dom: ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 react@19.2.5: - resolution: - { - integrity: sha512-llUJLzz1zTUBrskt2pwZgLq59AemifIftw4aB7JxOqf1HY2FDaGDxgwpAPVzHU1kdWabH7FauP4i1oEeer2WCA== - } - engines: { node: '>=0.10.0' } + resolution: {integrity: sha512-llUJLzz1zTUBrskt2pwZgLq59AemifIftw4aB7JxOqf1HY2FDaGDxgwpAPVzHU1kdWabH7FauP4i1oEeer2WCA==} + engines: {node: '>=0.10.0'} read-pkg-up@1.0.1: - resolution: - { - integrity: sha512-WD9MTlNtI55IwYUS27iHh9tK3YoIVhxis8yKhLpTqWtml739uXc9NWTpxoHkfZf3+DkCCsXox94/VWZniuZm6A== - } - engines: { node: '>=0.10.0' } + resolution: {integrity: sha512-WD9MTlNtI55IwYUS27iHh9tK3YoIVhxis8yKhLpTqWtml739uXc9NWTpxoHkfZf3+DkCCsXox94/VWZniuZm6A==} + engines: {node: '>=0.10.0'} read-pkg@1.1.0: - resolution: - { - integrity: sha512-7BGwRHqt4s/uVbuyoeejRn4YmFnYZiFl4AuaeXHlgZf3sONF0SOGlxs2Pw8g6hCKupo08RafIO5YXFNOKTfwsQ== - } - engines: { node: '>=0.10.0' } + resolution: {integrity: sha512-7BGwRHqt4s/uVbuyoeejRn4YmFnYZiFl4AuaeXHlgZf3sONF0SOGlxs2Pw8g6hCKupo08RafIO5YXFNOKTfwsQ==} + engines: {node: '>=0.10.0'} readable-stream@2.3.8: - resolution: - { - integrity: sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA== - } + resolution: {integrity: sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==} redent@1.0.0: - resolution: - { - integrity: sha512-qtW5hKzGQZqKoh6JNSD+4lfitfPKGz42e6QwiRmPM5mmKtR0N41AbJRYu0xJi7nhOJ4WDgRkKvAk6tw4WIwR4g== - } - engines: { node: '>=0.10.0' } + resolution: {integrity: sha512-qtW5hKzGQZqKoh6JNSD+4lfitfPKGz42e6QwiRmPM5mmKtR0N41AbJRYu0xJi7nhOJ4WDgRkKvAk6tw4WIwR4g==} + engines: {node: '>=0.10.0'} repeating@2.0.1: - resolution: - { - integrity: sha512-ZqtSMuVybkISo2OWvqvm7iHSWngvdaW3IpsT9/uP8v4gMi591LY6h35wdOfvQdWCKFWZWm2Y1Opp4kV7vQKT6A== - } - engines: { node: '>=0.10.0' } + resolution: {integrity: sha512-ZqtSMuVybkISo2OWvqvm7iHSWngvdaW3IpsT9/uP8v4gMi591LY6h35wdOfvQdWCKFWZWm2Y1Opp4kV7vQKT6A==} + engines: {node: '>=0.10.0'} replace-ext@1.0.1: - resolution: - { - integrity: sha512-yD5BHCe7quCgBph4rMQ+0KkIRKwWCrHDOX1p1Gp6HwjPM5kVoCdKGNhN7ydqqsX6lJEnQDKZ/tFMiEdQ1dvPEw== - } - engines: { node: '>= 0.10' } + resolution: {integrity: sha512-yD5BHCe7quCgBph4rMQ+0KkIRKwWCrHDOX1p1Gp6HwjPM5kVoCdKGNhN7ydqqsX6lJEnQDKZ/tFMiEdQ1dvPEw==} + engines: {node: '>= 0.10'} require-directory@2.1.1: - resolution: - { - integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q== - } - engines: { node: '>=0.10.0' } + resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} + engines: {node: '>=0.10.0'} resolve-from@4.0.0: - resolution: - { - integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g== - } - engines: { node: '>=4' } + resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} + engines: {node: '>=4'} resolve@1.22.12: - resolution: - { - integrity: sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA== - } - engines: { node: '>= 0.4' } + resolution: {integrity: sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA==} + engines: {node: '>= 0.4'} hasBin: true responselike@1.0.2: - resolution: - { - integrity: sha512-/Fpe5guzJk1gPqdJLJR5u7eG/gNY4nImjbRDaVWVMRhne55TCmj2i9Q+54PBRfatRC8v/rIiv9BN0pMd9OV5EQ== - } + resolution: {integrity: sha512-/Fpe5guzJk1gPqdJLJR5u7eG/gNY4nImjbRDaVWVMRhne55TCmj2i9Q+54PBRfatRC8v/rIiv9BN0pMd9OV5EQ==} reusify@1.1.0: - resolution: - { - integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw== - } - engines: { iojs: '>=1.0.0', node: '>=0.10.0' } + resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==} + engines: {iojs: '>=1.0.0', node: '>=0.10.0'} rimraf@2.7.1: - resolution: - { - integrity: sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w== - } + resolution: {integrity: sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==} deprecated: Rimraf versions prior to v4 are no longer supported hasBin: true rolldown@1.0.0-rc.17: - resolution: - { - integrity: sha512-ZrT53oAKrtA4+YtBWPQbtPOxIbVDbxT0orcYERKd63VJTF13zPcgXTvD4843L8pcsI7M6MErt8QtON6lrB9tyA== - } - engines: { node: ^20.19.0 || >=22.12.0 } + resolution: {integrity: sha512-ZrT53oAKrtA4+YtBWPQbtPOxIbVDbxT0orcYERKd63VJTF13zPcgXTvD4843L8pcsI7M6MErt8QtON6lrB9tyA==} + engines: {node: ^20.19.0 || >=22.12.0} hasBin: true rollup-plugin-visualizer@7.0.1: - resolution: - { - integrity: sha512-UJUT4+1Ho4OcWmPYU3sYXgUqI8B8Ayfe06MX7y0qCJ1K8aGoKtR/NDd/2nZqM7ADkrzny+I99Ul7GgyoiVNAgg== - } - engines: { node: '>=22' } + resolution: {integrity: sha512-UJUT4+1Ho4OcWmPYU3sYXgUqI8B8Ayfe06MX7y0qCJ1K8aGoKtR/NDd/2nZqM7ADkrzny+I99Ul7GgyoiVNAgg==} + engines: {node: '>=22'} hasBin: true peerDependencies: rolldown: 1.x || ^1.0.0-beta || ^1.0.0-rc @@ -4726,655 +2853,373 @@ packages: optional: true rollup@4.59.0: - resolution: - { - integrity: sha512-2oMpl67a3zCH9H79LeMcbDhXW/UmWG/y2zuqnF2jQq5uq9TbM9TVyXvA4+t+ne2IIkBdrLpAaRQAvo7YI/Yyeg== - } - engines: { node: '>=18.0.0', npm: '>=8.0.0' } + resolution: {integrity: sha512-2oMpl67a3zCH9H79LeMcbDhXW/UmWG/y2zuqnF2jQq5uq9TbM9TVyXvA4+t+ne2IIkBdrLpAaRQAvo7YI/Yyeg==} + engines: {node: '>=18.0.0', npm: '>=8.0.0'} hasBin: true run-applescript@7.1.0: - resolution: - { - integrity: sha512-DPe5pVFaAsinSaV6QjQ6gdiedWDcRCbUuiQfQa2wmWV7+xC9bGulGI8+TdRmoFkAPaBXk8CrAbnlY2ISniJ47Q== - } - engines: { node: '>=18' } + resolution: {integrity: sha512-DPe5pVFaAsinSaV6QjQ6gdiedWDcRCbUuiQfQa2wmWV7+xC9bGulGI8+TdRmoFkAPaBXk8CrAbnlY2ISniJ47Q==} + engines: {node: '>=18'} run-parallel@1.2.0: - resolution: - { - integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA== - } + resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} rxjs@7.8.2: - resolution: - { - integrity: sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA== - } + resolution: {integrity: sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==} safe-buffer@5.1.2: - resolution: - { - integrity: sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== - } + resolution: {integrity: sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==} safe-buffer@5.2.1: - resolution: - { - integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== - } + resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} sax@1.6.0: - resolution: - { - integrity: sha512-6R3J5M4AcbtLUdZmRv2SygeVaM7IhrLXu9BmnOGmmACak8fiUtOsYNWUS4uK7upbmHIBbLBeFeI//477BKLBzA== - } - engines: { node: '>=11.0.0' } + resolution: {integrity: sha512-6R3J5M4AcbtLUdZmRv2SygeVaM7IhrLXu9BmnOGmmACak8fiUtOsYNWUS4uK7upbmHIBbLBeFeI//477BKLBzA==} + engines: {node: '>=11.0.0'} scheduler@0.27.0: - resolution: - { - integrity: sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q== - } + resolution: {integrity: sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==} seek-bzip@1.0.6: - resolution: - { - integrity: sha512-e1QtP3YL5tWww8uKaOCQ18UxIT2laNBXHjV/S2WYCiK4udiv8lkG89KRIoCjUagnAmCBurjF4zEVX2ByBbnCjQ== - } + resolution: {integrity: sha512-e1QtP3YL5tWww8uKaOCQ18UxIT2laNBXHjV/S2WYCiK4udiv8lkG89KRIoCjUagnAmCBurjF4zEVX2ByBbnCjQ==} hasBin: true semver-regex@2.0.0: - resolution: - { - integrity: sha512-mUdIBBvdn0PLOeP3TEkMH7HHeUP3GjsXCwKarjv/kGmUFOYg1VqEemKhoQpWMu6X2I8kHeuVdGibLGkVK+/5Qw== - } - engines: { node: '>=6' } + resolution: {integrity: sha512-mUdIBBvdn0PLOeP3TEkMH7HHeUP3GjsXCwKarjv/kGmUFOYg1VqEemKhoQpWMu6X2I8kHeuVdGibLGkVK+/5Qw==} + engines: {node: '>=6'} semver-truncate@1.1.2: - resolution: - { - integrity: sha512-V1fGg9i4CL3qesB6U0L6XAm4xOJiHmt4QAacazumuasc03BvtFGIMCduv01JWQ69Nv+JST9TqhSCiJoxoY031w== - } - engines: { node: '>=0.10.0' } + resolution: {integrity: sha512-V1fGg9i4CL3qesB6U0L6XAm4xOJiHmt4QAacazumuasc03BvtFGIMCduv01JWQ69Nv+JST9TqhSCiJoxoY031w==} + engines: {node: '>=0.10.0'} semver@5.7.2: - resolution: - { - integrity: sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g== - } + resolution: {integrity: sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==} hasBin: true semver@6.3.1: - resolution: - { - integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== - } + resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} hasBin: true semver@7.7.4: - resolution: - { - integrity: sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA== - } - engines: { node: '>=10' } + resolution: {integrity: sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==} + engines: {node: '>=10'} hasBin: true set-cookie-parser@2.7.2: - resolution: - { - integrity: sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw== - } + resolution: {integrity: sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==} set-function-length@1.2.2: - resolution: - { - integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg== - } - engines: { node: '>= 0.4' } + resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==} + engines: {node: '>= 0.4'} shebang-command@1.2.0: - resolution: - { - integrity: sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg== - } - engines: { node: '>=0.10.0' } + resolution: {integrity: sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==} + engines: {node: '>=0.10.0'} shebang-command@2.0.0: - resolution: - { - integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA== - } - engines: { node: '>=8' } + resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} + engines: {node: '>=8'} shebang-regex@1.0.0: - resolution: - { - integrity: sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ== - } - engines: { node: '>=0.10.0' } + resolution: {integrity: sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ==} + engines: {node: '>=0.10.0'} shebang-regex@3.0.0: - resolution: - { - integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== - } - engines: { node: '>=8' } + resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} + engines: {node: '>=8'} shell-quote@1.8.3: - resolution: - { - integrity: sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw== - } - engines: { node: '>= 0.4' } + resolution: {integrity: sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw==} + engines: {node: '>= 0.4'} signal-exit@3.0.7: - resolution: - { - integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ== - } + resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} simple-code-frame@1.3.0: - resolution: - { - integrity: sha512-MB4pQmETUBlNs62BBeRjIFGeuy/x6gGKh7+eRUemn1rCFhqo7K+4slPqsyizCbcbYLnaYqaoZ2FWsZ/jN06D8w== - } + resolution: {integrity: sha512-MB4pQmETUBlNs62BBeRjIFGeuy/x6gGKh7+eRUemn1rCFhqo7K+4slPqsyizCbcbYLnaYqaoZ2FWsZ/jN06D8w==} slash@3.0.0: - resolution: - { - integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q== - } - engines: { node: '>=8' } + resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} + engines: {node: '>=8'} sort-keys-length@1.0.1: - resolution: - { - integrity: sha512-GRbEOUqCxemTAk/b32F2xa8wDTs+Z1QHOkbhJDQTvv/6G3ZkbJ+frYWsTcc7cBB3Fu4wy4XlLCuNtJuMn7Gsvw== - } - engines: { node: '>=0.10.0' } + resolution: {integrity: sha512-GRbEOUqCxemTAk/b32F2xa8wDTs+Z1QHOkbhJDQTvv/6G3ZkbJ+frYWsTcc7cBB3Fu4wy4XlLCuNtJuMn7Gsvw==} + engines: {node: '>=0.10.0'} sort-keys@1.1.2: - resolution: - { - integrity: sha512-vzn8aSqKgytVik0iwdBEi+zevbTYZogewTUM6dtpmGwEcdzbub/TX4bCzRhebDCRC3QzXgJsLRKB2V/Oof7HXg== - } - engines: { node: '>=0.10.0' } + resolution: {integrity: sha512-vzn8aSqKgytVik0iwdBEi+zevbTYZogewTUM6dtpmGwEcdzbub/TX4bCzRhebDCRC3QzXgJsLRKB2V/Oof7HXg==} + engines: {node: '>=0.10.0'} sort-keys@2.0.0: - resolution: - { - integrity: sha512-/dPCrG1s3ePpWm6yBbxZq5Be1dXGLyLn9Z791chDC3NFrpkVbWGzkBwPN1knaciexFXgRJ7hzdnwZ4stHSDmjg== - } - engines: { node: '>=4' } + resolution: {integrity: sha512-/dPCrG1s3ePpWm6yBbxZq5Be1dXGLyLn9Z791chDC3NFrpkVbWGzkBwPN1knaciexFXgRJ7hzdnwZ4stHSDmjg==} + engines: {node: '>=4'} source-map-js@1.2.1: - resolution: - { - integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA== - } - engines: { node: '>=0.10.0' } + resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} + engines: {node: '>=0.10.0'} source-map-support@0.5.21: - resolution: - { - integrity: sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w== - } + resolution: {integrity: sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==} source-map@0.5.7: - resolution: - { - integrity: sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ== - } - engines: { node: '>=0.10.0' } + resolution: {integrity: sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==} + engines: {node: '>=0.10.0'} source-map@0.6.1: - resolution: - { - integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== - } - engines: { node: '>=0.10.0' } + resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} + engines: {node: '>=0.10.0'} source-map@0.7.6: - resolution: - { - integrity: sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ== - } - engines: { node: '>= 12' } + resolution: {integrity: sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==} + engines: {node: '>= 12'} spdx-correct@3.2.0: - resolution: - { - integrity: sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA== - } + resolution: {integrity: sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==} spdx-exceptions@2.5.0: - resolution: - { - integrity: sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w== - } + resolution: {integrity: sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w==} spdx-expression-parse@3.0.1: - resolution: - { - integrity: sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q== - } + resolution: {integrity: sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==} spdx-license-ids@3.0.23: - resolution: - { - integrity: sha512-CWLcCCH7VLu13TgOH+r8p1O/Znwhqv/dbb6lqWy67G+pT1kHmeD/+V36AVb/vq8QMIQwVShJ6Ssl5FPh0fuSdw== - } + resolution: {integrity: sha512-CWLcCCH7VLu13TgOH+r8p1O/Znwhqv/dbb6lqWy67G+pT1kHmeD/+V36AVb/vq8QMIQwVShJ6Ssl5FPh0fuSdw==} squeak@1.3.0: - resolution: - { - integrity: sha512-YQL1ulInM+ev8nXX7vfXsCsDh6IqXlrremc1hzi77776BtpWgYJUMto3UM05GSAaGzJgWekszjoKDrVNB5XG+A== - } - engines: { node: '>=0.10.0' } + resolution: {integrity: sha512-YQL1ulInM+ev8nXX7vfXsCsDh6IqXlrremc1hzi77776BtpWgYJUMto3UM05GSAaGzJgWekszjoKDrVNB5XG+A==} + engines: {node: '>=0.10.0'} stable@0.1.8: - resolution: - { - integrity: sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w== - } + resolution: {integrity: sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w==} deprecated: 'Modern JS already guarantees Array#sort() is a stable sort, so this library is deprecated. See the compatibility table on MDN: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort#browser_compatibility' stack-trace@1.0.0-pre2: - resolution: - { - integrity: sha512-2ztBJRek8IVofG9DBJqdy2N5kulaacX30Nz7xmkYF6ale9WBVmIy6mFBchvGX7Vx/MyjBhx+Rcxqrj+dbOnQ6A== - } - engines: { node: '>=16' } + resolution: {integrity: sha512-2ztBJRek8IVofG9DBJqdy2N5kulaacX30Nz7xmkYF6ale9WBVmIy6mFBchvGX7Vx/MyjBhx+Rcxqrj+dbOnQ6A==} + engines: {node: '>=16'} strict-uri-encode@1.1.0: - resolution: - { - integrity: sha512-R3f198pcvnB+5IpnBlRkphuE9n46WyVl8I39W/ZUTZLz4nqSP/oLYUrcnJrw462Ds8he4YKMov2efsTIw1BDGQ== - } - engines: { node: '>=0.10.0' } + resolution: {integrity: sha512-R3f198pcvnB+5IpnBlRkphuE9n46WyVl8I39W/ZUTZLz4nqSP/oLYUrcnJrw462Ds8he4YKMov2efsTIw1BDGQ==} + engines: {node: '>=0.10.0'} string-width@4.2.3: - resolution: - { - integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== - } - engines: { node: '>=8' } + resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} + engines: {node: '>=8'} string-width@7.2.0: - resolution: - { - integrity: sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ== - } - engines: { node: '>=18' } + resolution: {integrity: sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==} + engines: {node: '>=18'} string_decoder@1.1.1: - resolution: - { - integrity: sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg== - } + resolution: {integrity: sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==} strip-ansi@3.0.1: - resolution: - { - integrity: sha512-VhumSSbBqDTP8p2ZLKj40UjBCV4+v8bUSEpUb4KjRgWk9pbqGF4REFj6KEagidb2f/M6AzC0EmFyDNGaw9OCzg== - } - engines: { node: '>=0.10.0' } + resolution: {integrity: sha512-VhumSSbBqDTP8p2ZLKj40UjBCV4+v8bUSEpUb4KjRgWk9pbqGF4REFj6KEagidb2f/M6AzC0EmFyDNGaw9OCzg==} + engines: {node: '>=0.10.0'} strip-ansi@6.0.1: - resolution: - { - integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== - } - engines: { node: '>=8' } + resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} + engines: {node: '>=8'} strip-ansi@7.2.0: - resolution: - { - integrity: sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w== - } - engines: { node: '>=12' } + resolution: {integrity: sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==} + engines: {node: '>=12'} strip-bom@2.0.0: - resolution: - { - integrity: sha512-kwrX1y7czp1E69n2ajbG65mIo9dqvJ+8aBQXOGVxqwvNbsXdFM6Lq37dLAY3mknUwru8CfcCbfOLL/gMo+fi3g== - } - engines: { node: '>=0.10.0' } + resolution: {integrity: sha512-kwrX1y7czp1E69n2ajbG65mIo9dqvJ+8aBQXOGVxqwvNbsXdFM6Lq37dLAY3mknUwru8CfcCbfOLL/gMo+fi3g==} + engines: {node: '>=0.10.0'} strip-dirs@2.1.0: - resolution: - { - integrity: sha512-JOCxOeKLm2CAS73y/U4ZeZPTkE+gNVCzKt7Eox84Iej1LT/2pTWYpZKJuxwQpvX1LiZb1xokNR7RLfuBAa7T3g== - } + resolution: {integrity: sha512-JOCxOeKLm2CAS73y/U4ZeZPTkE+gNVCzKt7Eox84Iej1LT/2pTWYpZKJuxwQpvX1LiZb1xokNR7RLfuBAa7T3g==} strip-eof@1.0.0: - resolution: - { - integrity: sha512-7FCwGGmx8mD5xQd3RPUvnSpUXHM3BWuzjtpD4TXsfcZ9EL4azvVVUscFYwD9nx8Kh+uCBC00XBtAykoMHwTh8Q== - } - engines: { node: '>=0.10.0' } + resolution: {integrity: sha512-7FCwGGmx8mD5xQd3RPUvnSpUXHM3BWuzjtpD4TXsfcZ9EL4azvVVUscFYwD9nx8Kh+uCBC00XBtAykoMHwTh8Q==} + engines: {node: '>=0.10.0'} strip-final-newline@2.0.0: - resolution: - { - integrity: sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA== - } - engines: { node: '>=6' } + resolution: {integrity: sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==} + engines: {node: '>=6'} strip-indent@1.0.1: - resolution: - { - integrity: sha512-I5iQq6aFMM62fBEAIB/hXzwJD6EEZ0xEGCX2t7oXqaKPIRgt4WruAQ285BISgdkP+HLGWyeGmNJcpIwFeRYRUA== - } - engines: { node: '>=0.10.0' } + resolution: {integrity: sha512-I5iQq6aFMM62fBEAIB/hXzwJD6EEZ0xEGCX2t7oXqaKPIRgt4WruAQ285BISgdkP+HLGWyeGmNJcpIwFeRYRUA==} + engines: {node: '>=0.10.0'} hasBin: true strip-outer@1.0.1: - resolution: - { - integrity: sha512-k55yxKHwaXnpYGsOzg4Vl8+tDrWylxDEpknGjhTiZB8dFRU5rTo9CAzeycivxV3s+zlTKwrs6WxMxR95n26kwg== - } - engines: { node: '>=0.10.0' } + resolution: {integrity: sha512-k55yxKHwaXnpYGsOzg4Vl8+tDrWylxDEpknGjhTiZB8dFRU5rTo9CAzeycivxV3s+zlTKwrs6WxMxR95n26kwg==} + engines: {node: '>=0.10.0'} strnum@1.1.2: - resolution: - { - integrity: sha512-vrN+B7DBIoTTZjnPNewwhx6cBA/H+IS7rfW68n7XxC1y7uoiGQBxaKzqucGUgavX15dJgiGztLJ8vxuEzwqBdA== - } + resolution: {integrity: sha512-vrN+B7DBIoTTZjnPNewwhx6cBA/H+IS7rfW68n7XxC1y7uoiGQBxaKzqucGUgavX15dJgiGztLJ8vxuEzwqBdA==} stylis@4.2.0: - resolution: - { - integrity: sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw== - } + resolution: {integrity: sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw==} supports-color@2.0.0: - resolution: - { - integrity: sha512-KKNVtd6pCYgPIKU4cp2733HWYCpplQhddZLBUryaAHou723x+FRzQ5Df824Fj+IyyuiQTRoub4SnIFfIcrp70g== - } - engines: { node: '>=0.8.0' } + resolution: {integrity: sha512-KKNVtd6pCYgPIKU4cp2733HWYCpplQhddZLBUryaAHou723x+FRzQ5Df824Fj+IyyuiQTRoub4SnIFfIcrp70g==} + engines: {node: '>=0.8.0'} supports-color@7.2.0: - resolution: - { - integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== - } - engines: { node: '>=8' } + resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} + engines: {node: '>=8'} supports-color@8.1.1: - resolution: - { - integrity: sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q== - } - engines: { node: '>=10' } + resolution: {integrity: sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==} + engines: {node: '>=10'} supports-preserve-symlinks-flag@1.0.0: - resolution: - { - integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== - } - engines: { node: '>= 0.4' } + resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} + engines: {node: '>= 0.4'} svgo@2.8.2: - resolution: - { - integrity: sha512-TyzE4NVGLUFy+H/Uy4N6c3G0HEeprsVfge6Lmq+0FdQQ/zqoVYB62IsBZORsiL+o96s6ff/V6/3UQo/C0cgCAA== - } - engines: { node: '>=10.13.0' } + resolution: {integrity: sha512-TyzE4NVGLUFy+H/Uy4N6c3G0HEeprsVfge6Lmq+0FdQQ/zqoVYB62IsBZORsiL+o96s6ff/V6/3UQo/C0cgCAA==} + engines: {node: '>=10.13.0'} hasBin: true tar-stream@1.6.2: - resolution: - { - integrity: sha512-rzS0heiNf8Xn7/mpdSVVSMAWAoy9bfb1WOTYC78Z0UQKeKa/CWS8FOq0lKGNa8DWKAn9gxjCvMLYc5PGXYlK2A== - } - engines: { node: '>= 0.8.0' } + resolution: {integrity: sha512-rzS0heiNf8Xn7/mpdSVVSMAWAoy9bfb1WOTYC78Z0UQKeKa/CWS8FOq0lKGNa8DWKAn9gxjCvMLYc5PGXYlK2A==} + engines: {node: '>= 0.8.0'} temp-dir@1.0.0: - resolution: - { - integrity: sha512-xZFXEGbG7SNC3itwBzI3RYjq/cEhBkx2hJuKGIUOcEULmkQExXiHat2z/qkISYsuR+IKumhEfKKbV5qXmhICFQ== - } - engines: { node: '>=4' } + resolution: {integrity: sha512-xZFXEGbG7SNC3itwBzI3RYjq/cEhBkx2hJuKGIUOcEULmkQExXiHat2z/qkISYsuR+IKumhEfKKbV5qXmhICFQ==} + engines: {node: '>=4'} tempfile@2.0.0: - resolution: - { - integrity: sha512-ZOn6nJUgvgC09+doCEF3oB+r3ag7kUvlsXEGX069QRD60p+P3uP7XG9N2/at+EyIRGSN//ZY3LyEotA1YpmjuA== - } - engines: { node: '>=4' } + resolution: {integrity: sha512-ZOn6nJUgvgC09+doCEF3oB+r3ag7kUvlsXEGX069QRD60p+P3uP7XG9N2/at+EyIRGSN//ZY3LyEotA1YpmjuA==} + engines: {node: '>=4'} terser@5.46.2: - resolution: - { - integrity: sha512-uxfo9fPcSgLDYob/w1FuL0c99MWiJDnv+5qXSQc5+Ki5NjVNsYi66INnMFBjf6uFz6OnX12piJQPF4IpjJTNTw== - } - engines: { node: '>=10' } + resolution: {integrity: sha512-uxfo9fPcSgLDYob/w1FuL0c99MWiJDnv+5qXSQc5+Ki5NjVNsYi66INnMFBjf6uFz6OnX12piJQPF4IpjJTNTw==} + engines: {node: '>=10'} hasBin: true through@2.3.8: - resolution: - { - integrity: sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg== - } + resolution: {integrity: sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==} timed-out@4.0.1: - resolution: - { - integrity: sha512-G7r3AhovYtr5YKOWQkta8RKAPb+J9IsO4uVmzjl8AZwfhs8UcUwTiD6gcJYSgOtzyjvQKrKYn41syHbUWMkafA== - } - engines: { node: '>=0.10.0' } + resolution: {integrity: sha512-G7r3AhovYtr5YKOWQkta8RKAPb+J9IsO4uVmzjl8AZwfhs8UcUwTiD6gcJYSgOtzyjvQKrKYn41syHbUWMkafA==} + engines: {node: '>=0.10.0'} tinyglobby@0.2.16: - resolution: - { - integrity: sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg== - } - engines: { node: '>=12.0.0' } + resolution: {integrity: sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==} + engines: {node: '>=12.0.0'} to-buffer@1.2.2: - resolution: - { - integrity: sha512-db0E3UJjcFhpDhAF4tLo03oli3pwl3dbnzXOUIlRKrp+ldk/VUxzpWYZENsw2SZiuBjHAk7DfB0VU7NKdpb6sw== - } - engines: { node: '>= 0.4' } + resolution: {integrity: sha512-db0E3UJjcFhpDhAF4tLo03oli3pwl3dbnzXOUIlRKrp+ldk/VUxzpWYZENsw2SZiuBjHAk7DfB0VU7NKdpb6sw==} + engines: {node: '>= 0.4'} to-regex-range@5.0.1: - resolution: - { - integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ== - } - engines: { node: '>=8.0' } + resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} + engines: {node: '>=8.0'} tree-kill@1.2.2: - resolution: - { - integrity: sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A== - } + resolution: {integrity: sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==} hasBin: true trim-newlines@1.0.0: - resolution: - { - integrity: sha512-Nm4cF79FhSTzrLKGDMi3I4utBtFv8qKy4sq1enftf2gMdpqI8oVQTAfySkTz5r49giVzDj88SVZXP4CeYQwjaw== - } - engines: { node: '>=0.10.0' } + resolution: {integrity: sha512-Nm4cF79FhSTzrLKGDMi3I4utBtFv8qKy4sq1enftf2gMdpqI8oVQTAfySkTz5r49giVzDj88SVZXP4CeYQwjaw==} + engines: {node: '>=0.10.0'} trim-repeated@1.0.0: - resolution: - { - integrity: sha512-pkonvlKk8/ZuR0D5tLW8ljt5I8kmxp2XKymhepUeOdCEfKpZaktSArkLHZt76OB1ZvO9bssUsDty4SWhLvZpLg== - } - engines: { node: '>=0.10.0' } + resolution: {integrity: sha512-pkonvlKk8/ZuR0D5tLW8ljt5I8kmxp2XKymhepUeOdCEfKpZaktSArkLHZt76OB1ZvO9bssUsDty4SWhLvZpLg==} + engines: {node: '>=0.10.0'} ts-api-utils@2.5.0: - resolution: - { - integrity: sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA== - } - engines: { node: '>=18.12' } + resolution: {integrity: sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==} + engines: {node: '>=18.12'} peerDependencies: typescript: '>=4.8.4' tslib@2.8.1: - resolution: - { - integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w== - } + resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} tunnel-agent@0.6.0: - resolution: - { - integrity: sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w== - } + resolution: {integrity: sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==} type-check@0.4.0: - resolution: - { - integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew== - } - engines: { node: '>= 0.8.0' } + resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} + engines: {node: '>= 0.8.0'} type-fest@0.11.0: - resolution: - { - integrity: sha512-OdjXJxnCN1AvyLSzeKIgXTXxV+99ZuXl3Hpo9XpJAv9MBcHrrJOQ5kV7ypXOuQie+AmWG25hLbiKdwYTifzcfQ== - } - engines: { node: '>=8' } + resolution: {integrity: sha512-OdjXJxnCN1AvyLSzeKIgXTXxV+99ZuXl3Hpo9XpJAv9MBcHrrJOQ5kV7ypXOuQie+AmWG25hLbiKdwYTifzcfQ==} + engines: {node: '>=8'} typed-array-buffer@1.0.3: - resolution: - { - integrity: sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw== - } - engines: { node: '>= 0.4' } + resolution: {integrity: sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==} + engines: {node: '>= 0.4'} typesafe-i18n@5.27.1: - resolution: - { - integrity: sha512-749uWo2ZXETT//kWjVYPm8QPYR8xLh8G0wLfoAyCAtAmysX67uCaAyLjAjAWojL6fuJpE5B6yIjwvO9orXzUPg== - } + resolution: {integrity: sha512-749uWo2ZXETT//kWjVYPm8QPYR8xLh8G0wLfoAyCAtAmysX67uCaAyLjAjAWojL6fuJpE5B6yIjwvO9orXzUPg==} hasBin: true peerDependencies: typescript: '>=3.5.1' typescript-eslint@8.59.1: - resolution: - { - integrity: sha512-xqDcFVBmlrltH64lklOVp1wYxgJr6LVdg3NamBgH2OOQDLFdTKfIZXF5PfghrnXQKXZGTQs8tr1vL7fJvq8CTQ== - } - engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + resolution: {integrity: sha512-xqDcFVBmlrltH64lklOVp1wYxgJr6LVdg3NamBgH2OOQDLFdTKfIZXF5PfghrnXQKXZGTQs8tr1vL7fJvq8CTQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 typescript: '>=4.8.4 <6.1.0' typescript@6.0.3: - resolution: - { - integrity: sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw== - } - engines: { node: '>=14.17' } + resolution: {integrity: sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==} + engines: {node: '>=14.17'} hasBin: true unbzip2-stream@1.4.3: - resolution: - { - integrity: sha512-mlExGW4w71ebDJviH16lQLtZS32VKqsSfk80GCfUlwT/4/hNRFsoscrF/c++9xinkMzECL1uL9DDwXqFWkruPg== - } + resolution: {integrity: sha512-mlExGW4w71ebDJviH16lQLtZS32VKqsSfk80GCfUlwT/4/hNRFsoscrF/c++9xinkMzECL1uL9DDwXqFWkruPg==} undici-types@7.19.2: - resolution: - { - integrity: sha512-qYVnV5OEm2AW8cJMCpdV20CDyaN3g0AjDlOGf1OW4iaDEx8MwdtChUp4zu4H0VP3nDRF/8RKWH+IPp9uW0YGZg== - } + resolution: {integrity: sha512-qYVnV5OEm2AW8cJMCpdV20CDyaN3g0AjDlOGf1OW4iaDEx8MwdtChUp4zu4H0VP3nDRF/8RKWH+IPp9uW0YGZg==} universalify@2.0.1: - resolution: - { - integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw== - } - engines: { node: '>= 10.0.0' } + resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==} + engines: {node: '>= 10.0.0'} update-browserslist-db@1.2.3: - resolution: - { - integrity: sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w== - } + resolution: {integrity: sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==} hasBin: true peerDependencies: browserslist: '>= 4.21.0' uri-js@4.4.1: - resolution: - { - integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg== - } + resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} url-parse-lax@1.0.0: - resolution: - { - integrity: sha512-BVA4lR5PIviy2PMseNd2jbFQ+jwSwQGdJejf5ctd1rEXt0Ypd7yanUK9+lYechVlN5VaTJGsu2U/3MDDu6KgBA== - } - engines: { node: '>=0.10.0' } + resolution: {integrity: sha512-BVA4lR5PIviy2PMseNd2jbFQ+jwSwQGdJejf5ctd1rEXt0Ypd7yanUK9+lYechVlN5VaTJGsu2U/3MDDu6KgBA==} + engines: {node: '>=0.10.0'} url-parse-lax@3.0.0: - resolution: - { - integrity: sha512-NjFKA0DidqPa5ciFcSrXnAltTtzz84ogy+NebPvfEgAck0+TNg4UJ4IN+fB7zRZfbgUf0syOo9MDxFkDSMuFaQ== - } - engines: { node: '>=4' } + resolution: {integrity: sha512-NjFKA0DidqPa5ciFcSrXnAltTtzz84ogy+NebPvfEgAck0+TNg4UJ4IN+fB7zRZfbgUf0syOo9MDxFkDSMuFaQ==} + engines: {node: '>=4'} url-to-options@1.0.1: - resolution: - { - integrity: sha512-0kQLIzG4fdk/G5NONku64rSH/x32NOA39LVQqlK8Le6lvTF6GGRJpqaQFGgU+CLwySIqBSMdwYM0sYcW9f6P4A== - } - engines: { node: '>= 4' } + resolution: {integrity: sha512-0kQLIzG4fdk/G5NONku64rSH/x32NOA39LVQqlK8Le6lvTF6GGRJpqaQFGgU+CLwySIqBSMdwYM0sYcW9f6P4A==} + engines: {node: '>= 4'} util-deprecate@1.0.2: - resolution: - { - integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw== - } + resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} uuid@3.4.0: - resolution: - { - integrity: sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A== - } - deprecated: Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details. + resolution: {integrity: sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==} + deprecated: uuid@10 and below is no longer supported. For ESM codebases, update to uuid@latest. For CommonJS codebases, use uuid@11 (but be aware this version will likely be deprecated in 2028). hasBin: true validate-npm-package-license@3.0.4: - resolution: - { - integrity: sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew== - } + resolution: {integrity: sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==} vite-plugin-imagemin@0.6.1: - resolution: - { - integrity: sha512-cP7LDn8euPrji7WYtDoNQpJEB9nkMxJHm/A+QZnvMrrCSuyo/clpMy/T1v7suDXPBavsDiDdFdVQB5p7VGD2cg== - } + resolution: {integrity: sha512-cP7LDn8euPrji7WYtDoNQpJEB9nkMxJHm/A+QZnvMrrCSuyo/clpMy/T1v7suDXPBavsDiDdFdVQB5p7VGD2cg==} peerDependencies: vite: '>=2.0.0' vite-prerender-plugin@0.5.13: - resolution: - { - integrity: sha512-IKSpYkzDBsKAxa05naRbj7GvNVMSdww/Z/E89oO3xndz+gWnOBOKOAbEXv7qDhktY/j3vHgJmoV1pPzqU2tx9g== - } + resolution: {integrity: sha512-IKSpYkzDBsKAxa05naRbj7GvNVMSdww/Z/E89oO3xndz+gWnOBOKOAbEXv7qDhktY/j3vHgJmoV1pPzqU2tx9g==} peerDependencies: vite: 5.x || 6.x || 7.x || 8.x vite@8.0.10: - resolution: - { - integrity: sha512-rZuUu9j6J5uotLDs+cAA4O5H4K1SfPliUlQwqa6YEwSrWDZzP4rhm00oJR5snMewjxF5V/K3D4kctsUTsIU9Mw== - } - engines: { node: ^20.19.0 || >=22.12.0 } + resolution: {integrity: sha512-rZuUu9j6J5uotLDs+cAA4O5H4K1SfPliUlQwqa6YEwSrWDZzP4rhm00oJR5snMewjxF5V/K3D4kctsUTsIU9Mw==} + engines: {node: ^20.19.0 || >=22.12.0} hasBin: true peerDependencies: '@types/node': ^20.19.0 || >=22.12.0 @@ -5416,142 +3261,83 @@ packages: optional: true which-typed-array@1.1.20: - resolution: - { - integrity: sha512-LYfpUkmqwl0h9A2HL09Mms427Q1RZWuOHsukfVcKRq9q95iQxdw0ix1JQrqbcDR9PH1QDwf5Qo8OZb5lksZ8Xg== - } - engines: { node: '>= 0.4' } + resolution: {integrity: sha512-LYfpUkmqwl0h9A2HL09Mms427Q1RZWuOHsukfVcKRq9q95iQxdw0ix1JQrqbcDR9PH1QDwf5Qo8OZb5lksZ8Xg==} + engines: {node: '>= 0.4'} which@1.3.1: - resolution: - { - integrity: sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ== - } + resolution: {integrity: sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==} hasBin: true which@2.0.2: - resolution: - { - integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== - } - engines: { node: '>= 8' } + resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} + engines: {node: '>= 8'} hasBin: true word-wrap@1.2.5: - resolution: - { - integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA== - } - engines: { node: '>=0.10.0' } + resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} + engines: {node: '>=0.10.0'} wrap-ansi@7.0.0: - resolution: - { - integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== - } - engines: { node: '>=10' } + resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} + engines: {node: '>=10'} wrap-ansi@9.0.2: - resolution: - { - integrity: sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww== - } - engines: { node: '>=18' } + resolution: {integrity: sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==} + engines: {node: '>=18'} wrappy@1.0.2: - resolution: - { - integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ== - } + resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} wsl-utils@0.3.1: - resolution: - { - integrity: sha512-g/eziiSUNBSsdDJtCLB8bdYEUMj4jR7AGeUo96p/3dTafgjHhpF4RiCFPiRILwjQoDXx5MqkBr4fwWtR3Ky4Wg== - } - engines: { node: '>=20' } + resolution: {integrity: sha512-g/eziiSUNBSsdDJtCLB8bdYEUMj4jR7AGeUo96p/3dTafgjHhpF4RiCFPiRILwjQoDXx5MqkBr4fwWtR3Ky4Wg==} + engines: {node: '>=20'} xtend@4.0.2: - resolution: - { - integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ== - } - engines: { node: '>=0.4' } + resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==} + engines: {node: '>=0.4'} y18n@5.0.8: - resolution: - { - integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA== - } - engines: { node: '>=10' } + resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} + engines: {node: '>=10'} yallist@2.1.2: - resolution: - { - integrity: sha512-ncTzHV7NvsQZkYe1DW7cbDLm0YpzHmZF5r/iyP3ZnQtMiJ+pjzisCiMNI+Sj+xQF5pXhSHxSB3uDbsBTzY/c2A== - } + resolution: {integrity: sha512-ncTzHV7NvsQZkYe1DW7cbDLm0YpzHmZF5r/iyP3ZnQtMiJ+pjzisCiMNI+Sj+xQF5pXhSHxSB3uDbsBTzY/c2A==} yallist@3.1.1: - resolution: - { - integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g== - } + resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} yaml@1.10.3: - resolution: - { - integrity: sha512-vIYeF1u3CjlhAFekPPAk2h/Kv4T3mAkMox5OymRiJQB0spDP10LHvt+K7G9Ny6NuuMAb25/6n1qyUjAcGNf/AA== - } - engines: { node: '>= 6' } + resolution: {integrity: sha512-vIYeF1u3CjlhAFekPPAk2h/Kv4T3mAkMox5OymRiJQB0spDP10LHvt+K7G9Ny6NuuMAb25/6n1qyUjAcGNf/AA==} + engines: {node: '>= 6'} yargs-parser@21.1.1: - resolution: - { - integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw== - } - engines: { node: '>=12' } + resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} + engines: {node: '>=12'} yargs-parser@22.0.0: - resolution: - { - integrity: sha512-rwu/ClNdSMpkSrUb+d6BRsSkLUq1fmfsY6TOpYzTwvwkg1/NRG85KBy3kq++A8LKQwX6lsu+aWad+2khvuXrqw== - } - engines: { node: ^20.19.0 || ^22.12.0 || >=23 } + resolution: {integrity: sha512-rwu/ClNdSMpkSrUb+d6BRsSkLUq1fmfsY6TOpYzTwvwkg1/NRG85KBy3kq++A8LKQwX6lsu+aWad+2khvuXrqw==} + engines: {node: ^20.19.0 || ^22.12.0 || >=23} yargs@17.7.2: - resolution: - { - integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w== - } - engines: { node: '>=12' } + resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==} + engines: {node: '>=12'} yargs@18.0.0: - resolution: - { - integrity: sha512-4UEqdc2RYGHZc7Doyqkrqiln3p9X2DZVxaGbwhn2pi7MrRagKaOcIKe8L3OxYcbhXLgLFUS3zAYuQjKBQgmuNg== - } - engines: { node: ^20.19.0 || ^22.12.0 || >=23 } + resolution: {integrity: sha512-4UEqdc2RYGHZc7Doyqkrqiln3p9X2DZVxaGbwhn2pi7MrRagKaOcIKe8L3OxYcbhXLgLFUS3zAYuQjKBQgmuNg==} + engines: {node: ^20.19.0 || ^22.12.0 || >=23} yauzl@2.10.0: - resolution: - { - integrity: sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g== - } + resolution: {integrity: sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==} yocto-queue@0.1.0: - resolution: - { - integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q== - } - engines: { node: '>=10' } + resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} + engines: {node: '>=10'} zimmerframe@1.1.4: - resolution: - { - integrity: sha512-B58NGBEoc8Y9MWWCQGl/gq9xBCe4IiKM0a2x7GZdQKOW5Exr8S1W24J6OgM1njK8xCRGvAJIL/MxXHf6SkmQKQ== - } + resolution: {integrity: sha512-B58NGBEoc8Y9MWWCQGl/gq9xBCe4IiKM0a2x7GZdQKOW5Exr8S1W24J6OgM1njK8xCRGvAJIL/MxXHf6SkmQKQ==} snapshots: + '@alova/adapter-xhr@2.3.1(alova@3.5.1)': dependencies: '@alova/shared': 1.3.2 @@ -5565,7 +3351,7 @@ snapshots: js-tokens: 4.0.0 picocolors: 1.1.1 - '@babel/compat-data@7.29.0': {} + '@babel/compat-data@7.29.3': {} '@babel/core@7.29.0': dependencies: @@ -5574,7 +3360,7 @@ snapshots: '@babel/helper-compilation-targets': 7.28.6 '@babel/helper-module-transforms': 7.28.6(@babel/core@7.29.0) '@babel/helpers': 7.29.2 - '@babel/parser': 7.29.2 + '@babel/parser': 7.29.3 '@babel/template': 7.28.6 '@babel/traverse': 7.29.0 '@babel/types': 7.29.0 @@ -5589,7 +3375,7 @@ snapshots: '@babel/generator@7.29.1': dependencies: - '@babel/parser': 7.29.2 + '@babel/parser': 7.29.3 '@babel/types': 7.29.0 '@jridgewell/gen-mapping': 0.3.13 '@jridgewell/trace-mapping': 0.3.31 @@ -5601,7 +3387,7 @@ snapshots: '@babel/helper-compilation-targets@7.28.6': dependencies: - '@babel/compat-data': 7.29.0 + '@babel/compat-data': 7.29.3 '@babel/helper-validator-option': 7.27.1 browserslist: 4.28.2 lru-cache: 5.1.1 @@ -5638,7 +3424,7 @@ snapshots: '@babel/template': 7.28.6 '@babel/types': 7.29.0 - '@babel/parser@7.29.2': + '@babel/parser@7.29.3': dependencies: '@babel/types': 7.29.0 @@ -5670,7 +3456,7 @@ snapshots: '@babel/template@7.28.6': dependencies: '@babel/code-frame': 7.29.0 - '@babel/parser': 7.29.2 + '@babel/parser': 7.29.3 '@babel/types': 7.29.0 '@babel/traverse@7.29.0': @@ -5678,7 +3464,7 @@ snapshots: '@babel/code-frame': 7.29.0 '@babel/generator': 7.29.1 '@babel/helper-globals': 7.28.0 - '@babel/parser': 7.29.2 + '@babel/parser': 7.29.3 '@babel/template': 7.28.6 '@babel/types': 7.29.0 debug: 4.4.3 @@ -5870,9 +3656,9 @@ snapshots: '@esbuild/win32-x64@0.27.4': optional: true - '@eslint-community/eslint-utils@4.9.1(eslint@10.2.1)': + '@eslint-community/eslint-utils@4.9.1(eslint@10.3.0)': dependencies: - eslint: 10.2.1 + eslint: 10.3.0 eslint-visitor-keys: 3.4.3 '@eslint-community/regexpp@4.12.2': {} @@ -5893,9 +3679,9 @@ snapshots: dependencies: '@types/json-schema': 7.0.15 - '@eslint/js@10.0.1(eslint@10.2.1)': + '@eslint/js@10.0.1(eslint@10.3.0)': optionalDependencies: - eslint: 10.2.1 + eslint: 10.3.0 '@eslint/object-schema@3.0.5': {} @@ -6246,7 +4032,7 @@ snapshots: '@trivago/prettier-plugin-sort-imports@6.0.2(prettier@3.8.3)': dependencies: '@babel/generator': 7.29.1 - '@babel/parser': 7.29.2 + '@babel/parser': 7.29.3 '@babel/traverse': 7.29.0 '@babel/types': 7.29.0 javascript-natural-sort: 0.7.1 @@ -6338,15 +4124,15 @@ snapshots: dependencies: '@types/node': 25.6.0 - '@typescript-eslint/eslint-plugin@8.59.1(@typescript-eslint/parser@8.59.1(eslint@10.2.1)(typescript@6.0.3))(eslint@10.2.1)(typescript@6.0.3)': + '@typescript-eslint/eslint-plugin@8.59.1(@typescript-eslint/parser@8.59.1(eslint@10.3.0)(typescript@6.0.3))(eslint@10.3.0)(typescript@6.0.3)': dependencies: '@eslint-community/regexpp': 4.12.2 - '@typescript-eslint/parser': 8.59.1(eslint@10.2.1)(typescript@6.0.3) + '@typescript-eslint/parser': 8.59.1(eslint@10.3.0)(typescript@6.0.3) '@typescript-eslint/scope-manager': 8.59.1 - '@typescript-eslint/type-utils': 8.59.1(eslint@10.2.1)(typescript@6.0.3) - '@typescript-eslint/utils': 8.59.1(eslint@10.2.1)(typescript@6.0.3) + '@typescript-eslint/type-utils': 8.59.1(eslint@10.3.0)(typescript@6.0.3) + '@typescript-eslint/utils': 8.59.1(eslint@10.3.0)(typescript@6.0.3) '@typescript-eslint/visitor-keys': 8.59.1 - eslint: 10.2.1 + eslint: 10.3.0 ignore: 7.0.5 natural-compare: 1.4.0 ts-api-utils: 2.5.0(typescript@6.0.3) @@ -6354,14 +4140,14 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/parser@8.59.1(eslint@10.2.1)(typescript@6.0.3)': + '@typescript-eslint/parser@8.59.1(eslint@10.3.0)(typescript@6.0.3)': dependencies: '@typescript-eslint/scope-manager': 8.59.1 '@typescript-eslint/types': 8.59.1 '@typescript-eslint/typescript-estree': 8.59.1(typescript@6.0.3) '@typescript-eslint/visitor-keys': 8.59.1 debug: 4.4.3 - eslint: 10.2.1 + eslint: 10.3.0 typescript: 6.0.3 transitivePeerDependencies: - supports-color @@ -6384,13 +4170,13 @@ snapshots: dependencies: typescript: 6.0.3 - '@typescript-eslint/type-utils@8.59.1(eslint@10.2.1)(typescript@6.0.3)': + '@typescript-eslint/type-utils@8.59.1(eslint@10.3.0)(typescript@6.0.3)': dependencies: '@typescript-eslint/types': 8.59.1 '@typescript-eslint/typescript-estree': 8.59.1(typescript@6.0.3) - '@typescript-eslint/utils': 8.59.1(eslint@10.2.1)(typescript@6.0.3) + '@typescript-eslint/utils': 8.59.1(eslint@10.3.0)(typescript@6.0.3) debug: 4.4.3 - eslint: 10.2.1 + eslint: 10.3.0 ts-api-utils: 2.5.0(typescript@6.0.3) typescript: 6.0.3 transitivePeerDependencies: @@ -6413,13 +4199,13 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/utils@8.59.1(eslint@10.2.1)(typescript@6.0.3)': + '@typescript-eslint/utils@8.59.1(eslint@10.3.0)(typescript@6.0.3)': dependencies: - '@eslint-community/eslint-utils': 4.9.1(eslint@10.2.1) + '@eslint-community/eslint-utils': 4.9.1(eslint@10.3.0) '@typescript-eslint/scope-manager': 8.59.1 '@typescript-eslint/types': 8.59.1 '@typescript-eslint/typescript-estree': 8.59.1(typescript@6.0.3) - eslint: 10.2.1 + eslint: 10.3.0 typescript: 6.0.3 transitivePeerDependencies: - supports-color @@ -6493,7 +4279,7 @@ snapshots: base64-js@1.5.1: {} - baseline-browser-mapping@2.10.24: {} + baseline-browser-mapping@2.10.25: {} bin-build@3.0.0: dependencies: @@ -6554,9 +4340,9 @@ snapshots: browserslist@4.28.2: dependencies: - baseline-browser-mapping: 2.10.24 + baseline-browser-mapping: 2.10.25 caniuse-lite: 1.0.30001791 - electron-to-chromium: 1.5.344 + electron-to-chromium: 1.5.349 node-releases: 2.0.38 update-browserslist-db: 1.2.3(browserslist@4.28.2) @@ -6915,7 +4701,7 @@ snapshots: duplexer3@0.1.5: {} - electron-to-chromium@1.5.344: {} + electron-to-chromium@1.5.349: {} emoji-regex@10.6.0: {} @@ -7061,9 +4847,9 @@ snapshots: escape-string-regexp@4.0.0: {} - eslint-config-prettier@10.1.8(eslint@10.2.1): + eslint-config-prettier@10.1.8(eslint@10.3.0): dependencies: - eslint: 10.2.1 + eslint: 10.3.0 eslint-scope@9.1.2: dependencies: @@ -7076,9 +4862,9 @@ snapshots: eslint-visitor-keys@5.0.1: {} - eslint@10.2.1: + eslint@10.3.0: dependencies: - '@eslint-community/eslint-utils': 4.9.1(eslint@10.2.1) + '@eslint-community/eslint-utils': 4.9.1(eslint@10.3.0) '@eslint-community/regexpp': 4.12.2 '@eslint/config-array': 0.23.5 '@eslint/config-helpers': 0.5.5 @@ -7868,7 +5654,7 @@ snapshots: ms@2.1.3: {} - nanoid@3.3.11: {} + nanoid@3.3.12: {} natural-compare@1.4.0: {} @@ -8065,9 +5851,9 @@ snapshots: possible-typed-array-names@1.1.0: {} - postcss@8.5.12: + postcss@8.5.13: dependencies: - nanoid: 3.3.11 + nanoid: 3.3.12 picocolors: 1.1.1 source-map-js: 1.2.1 @@ -8547,13 +6333,13 @@ snapshots: dependencies: typescript: 6.0.3 - typescript-eslint@8.59.1(eslint@10.2.1)(typescript@6.0.3): + typescript-eslint@8.59.1(eslint@10.3.0)(typescript@6.0.3): dependencies: - '@typescript-eslint/eslint-plugin': 8.59.1(@typescript-eslint/parser@8.59.1(eslint@10.2.1)(typescript@6.0.3))(eslint@10.2.1)(typescript@6.0.3) - '@typescript-eslint/parser': 8.59.1(eslint@10.2.1)(typescript@6.0.3) + '@typescript-eslint/eslint-plugin': 8.59.1(@typescript-eslint/parser@8.59.1(eslint@10.3.0)(typescript@6.0.3))(eslint@10.3.0)(typescript@6.0.3) + '@typescript-eslint/parser': 8.59.1(eslint@10.3.0)(typescript@6.0.3) '@typescript-eslint/typescript-estree': 8.59.1(typescript@6.0.3) - '@typescript-eslint/utils': 8.59.1(eslint@10.2.1)(typescript@6.0.3) - eslint: 10.2.1 + '@typescript-eslint/utils': 8.59.1(eslint@10.3.0)(typescript@6.0.3) + eslint: 10.3.0 typescript: 6.0.3 transitivePeerDependencies: - supports-color @@ -8641,7 +6427,7 @@ snapshots: dependencies: lightningcss: 1.32.0 picomatch: 4.0.4 - postcss: 8.5.12 + postcss: 8.5.13 rolldown: 1.0.0-rc.17 tinyglobby: 0.2.16 optionalDependencies: diff --git a/mock-api/pnpm-lock.yaml b/mock-api/pnpm-lock.yaml index d1f155ffa..1ddedb1b1 100644 --- a/mock-api/pnpm-lock.yaml +++ b/mock-api/pnpm-lock.yaml @@ -46,8 +46,8 @@ packages: resolution: {integrity: sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==} engines: {node: '>=6.9.0'} - '@babel/parser@7.29.2': - resolution: {integrity: sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA==} + '@babel/parser@7.29.3': + resolution: {integrity: sha512-b3ctpQwp+PROvU/cttc4OYl4MzfJUWy6FZg+PMXfzmt/+39iHVF0sDfqay8TQM3JA2EUOyKcFZt75jWriQijsA==} engines: {node: '>=6.0.0'} hasBin: true @@ -185,7 +185,7 @@ snapshots: '@babel/generator@7.29.1': dependencies: - '@babel/parser': 7.29.2 + '@babel/parser': 7.29.3 '@babel/types': 7.29.0 '@jridgewell/gen-mapping': 0.3.13 '@jridgewell/trace-mapping': 0.3.31 @@ -197,14 +197,14 @@ snapshots: '@babel/helper-validator-identifier@7.28.5': {} - '@babel/parser@7.29.2': + '@babel/parser@7.29.3': dependencies: '@babel/types': 7.29.0 '@babel/template@7.28.6': dependencies: '@babel/code-frame': 7.29.0 - '@babel/parser': 7.29.2 + '@babel/parser': 7.29.3 '@babel/types': 7.29.0 '@babel/traverse@7.29.0': @@ -212,7 +212,7 @@ snapshots: '@babel/code-frame': 7.29.0 '@babel/generator': 7.29.1 '@babel/helper-globals': 7.28.0 - '@babel/parser': 7.29.2 + '@babel/parser': 7.29.3 '@babel/template': 7.28.6 '@babel/types': 7.29.0 debug: 4.4.3 @@ -249,7 +249,7 @@ snapshots: '@trivago/prettier-plugin-sort-imports@6.0.2(prettier@3.8.3)': dependencies: '@babel/generator': 7.29.1 - '@babel/parser': 7.29.2 + '@babel/parser': 7.29.3 '@babel/traverse': 7.29.0 '@babel/types': 7.29.0 javascript-natural-sort: 0.7.1 From 3062d3f0e3389aade19aa96d52acffa62e43d08f Mon Sep 17 00:00:00 2001 From: proddy Date: Sat, 2 May 2026 09:48:44 +0200 Subject: [PATCH 42/48] remove Divider --- interface/src/app/settings/Version.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/interface/src/app/settings/Version.tsx b/interface/src/app/settings/Version.tsx index 373133a0a..c29dd33fc 100644 --- a/interface/src/app/settings/Version.tsx +++ b/interface/src/app/settings/Version.tsx @@ -17,7 +17,6 @@ import { DialogActions, DialogContent, DialogTitle, - Divider, Grid, IconButton, Table, From 323fc1bb99a828daee3efb16246340b60bd1efd2 Mon Sep 17 00:00:00 2001 From: proddy Date: Sat, 2 May 2026 09:48:55 +0200 Subject: [PATCH 43/48] remove comments --- interface/src/contexts/authentication/Authentication.tsx | 2 -- 1 file changed, 2 deletions(-) diff --git a/interface/src/contexts/authentication/Authentication.tsx b/interface/src/contexts/authentication/Authentication.tsx index 669f06e6a..af0a0055c 100644 --- a/interface/src/contexts/authentication/Authentication.tsx +++ b/interface/src/contexts/authentication/Authentication.tsx @@ -81,8 +81,6 @@ const Authentication: FC = ({ children }) => { setMe(undefined); setInitialized(true); } - // refreshVersions and sendVerifyAuthorization are stable - // eslint-disable-next-line react-hooks/exhaustive-deps }, []); useEffect(() => { From f998714225815e3e49310390928850e028e8e294 Mon Sep 17 00:00:00 2001 From: proddy Date: Sat, 2 May 2026 09:49:09 +0200 Subject: [PATCH 44/48] add missing #endif --- src/core/system.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/core/system.cpp b/src/core/system.cpp index 64700323b..b420e13b1 100644 --- a/src/core/system.cpp +++ b/src/core/system.cpp @@ -988,6 +988,7 @@ void System::heartbeat_json(JsonObject output) { #ifndef EMSESP_STANDALONE output["freemem"] = getHeapMem(); output["max_alloc"] = getMaxAllocMem(); +#endif #if CONFIG_IDF_TARGET_ESP32S3 || CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32S2 output["temperature"] = (int)temperature_; #endif From 132f83aa7938a486cf5a130263ef2f6033be9aaa Mon Sep 17 00:00:00 2001 From: proddy Date: Sat, 2 May 2026 09:49:21 +0200 Subject: [PATCH 45/48] update dictionary --- project-words.txt | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/project-words.txt b/project-words.txt index 7a998c037..0ffcdb986 100644 --- a/project-words.txt +++ b/project-words.txt @@ -1330,4 +1330,8 @@ zyxwvutsrqponmlkjihgfedcba öffnen česky živanović -MWDT \ No newline at end of file +MWDT +juststopped +handshaked +startm +netifs From 363799c9c61add38c6ada6a1c93989abf9c79e25 Mon Sep 17 00:00:00 2001 From: proddy Date: Sat, 2 May 2026 09:49:34 +0200 Subject: [PATCH 46/48] fix connect spelling --- src/core/network.cpp | 16 ++++++++-------- src/core/network.h | 2 +- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/core/network.cpp b/src/core/network.cpp index 74534642b..46045c211 100644 --- a/src/core/network.cpp +++ b/src/core/network.cpp @@ -591,11 +591,11 @@ bool Network::findNetworks() { // if we have a connection and it's a new one, set it up if (best_iface != NetIface::NONE && best_iface != previous_iface) { - previous_iface = network_iface_; // save the previous interface for comparison next time - network_ip_ = info.ip.addr; - network_iface_ = iface_from_desc(info.desc); // "sta"/"ap"/"eth*" - has_ipv6_ = info.has_ipv6; - connnect_retry_ = 0; + previous_iface = network_iface_; // save the previous interface for comparison next time + network_ip_ = info.ip.addr; + network_iface_ = iface_from_desc(info.desc); // "sta"/"ap"/"eth*" + has_ipv6_ = info.has_ipv6; + connect_retry_ = 0; LOG_INFO("Network connected via %s (IP: " IPSTR ")", network_iface_ == NetIface::ETHERNET ? "Ethernet" @@ -628,8 +628,8 @@ bool Network::findNetworks() { network_ip_ = 0; network_iface_ = NetIface::NONE; has_ipv6_ = false; - connnect_retry_++; - LOG_DEBUG("No active network interfaces found yet, re-connection count %d", connnect_retry_); + connect_retry_++; + LOG_DEBUG("No active network interfaces found yet, re-connection count %d", connect_retry_); #endif return false; // no connection found yet } @@ -638,7 +638,7 @@ bool Network::findNetworks() { void Network::startAP() { #ifndef EMSESP_STANDALONE // Only start AP as a fallback if the Network has failed - if (connnect_retry_ < MAX_NETWORK_RECONNECTION_ATTEMPTS) { + if (connect_retry_ < MAX_NETWORK_RECONNECTION_ATTEMPTS) { return; } diff --git a/src/core/network.h b/src/core/network.h index 296187298..577f3f5a4 100644 --- a/src/core/network.h +++ b/src/core/network.h @@ -176,7 +176,7 @@ class Network { bool juststopped_ = false; bool eth_started_ = false; // true after ETH.begin() has succeeded once; prevents repeated re-init while DHCP is still running volatile uint8_t last_disconnect_reason_ = 0; - uint16_t connnect_retry_ = 0; // number of network re-connection attempts + uint16_t connect_retry_ = 0; // number of network re-connection attempts volatile bool wifi_connect_pending_ = false; // Network and AP settings From cb4cb393966118bc85a9cfdefbbe98c432493c6b Mon Sep 17 00:00:00 2001 From: proddy Date: Sat, 2 May 2026 10:43:36 +0200 Subject: [PATCH 47/48] update --- CHANGELOG_LATEST.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG_LATEST.md b/CHANGELOG_LATEST.md index 7427a8c89..92abb9b40 100644 --- a/CHANGELOG_LATEST.md +++ b/CHANGELOG_LATEST.md @@ -2,7 +2,7 @@ For more details go to [emsesp.org](https://emsesp.org/). -## [3.8.2] +## [3.9.0] ## Added @@ -15,6 +15,7 @@ For more details go to [emsesp.org](https://emsesp.org/). - e-mail notification using ReadyMail Client - 2.nd freshwater module (dhw4, dhw5) [#2991](https://github.com/emsesp/EMS-ESP32/issues/2991) - full system backup and restore +- updated version check [#3047](https://github.com/emsesp/EMS-ESP32/issues/3047) ## Fixed @@ -37,3 +38,4 @@ For more details go to [emsesp.org](https://emsesp.org/). - fetch telegrams: set length to fetch [#3017](https://github.com/emsesp/EMS-ESP32/issues/3017) - move http client from stack to heap - heap optimizations [#3021](https://github.com/emsesp/EMS-ESP32/discussions/3021) +- refactored network code into a single class [#3052](https://github.com/emsesp/EMS-ESP32/pull/3052) \ No newline at end of file From 3cc3c74e5ae3427d1b7168e7f7aa5850ff48287c Mon Sep 17 00:00:00 2001 From: proddy Date: Sat, 2 May 2026 10:44:06 +0200 Subject: [PATCH 48/48] update versions --- interface/package.json | 2 +- mock-api/package.json | 2 +- mock-api/restServer.ts | 40 +++++++++++++++++++++------------------- sonar-project.properties | 2 +- 4 files changed, 24 insertions(+), 22 deletions(-) diff --git a/interface/package.json b/interface/package.json index 38bef288e..019c20056 100644 --- a/interface/package.json +++ b/interface/package.json @@ -1,6 +1,6 @@ { "name": "EMS-ESP", - "version": "3.8.2", + "version": "3.9.0", "description": "EMS-ESP WebUI", "homepage": "https://emsesp.org", "author": "emsesp.org", diff --git a/mock-api/package.json b/mock-api/package.json index b294dba5a..fec2473af 100644 --- a/mock-api/package.json +++ b/mock-api/package.json @@ -1,6 +1,6 @@ { "name": "mock-api", - "version": "3.8.2", + "version": "3.9.0", "description": "mock api for EMS-ESP", "author": "proddy, emsesp.org", "license": "MIT", diff --git a/mock-api/restServer.ts b/mock-api/restServer.ts index 22e2c72b3..f062fb3ff 100644 --- a/mock-api/restServer.ts +++ b/mock-api/restServer.ts @@ -301,10 +301,10 @@ function updateMask(entity: any, de: any, dd: any) { const old_custom_name = dd.nodes[dd_objIndex].cn; console.log( 'comparing names, old (' + - old_custom_name + - ') with new (' + - new_custom_name + - ')' + old_custom_name + + ') with new (' + + new_custom_name + + ')' ); if (old_custom_name !== new_custom_name) { changed = true; @@ -402,15 +402,17 @@ function upgradeImportantMessages(version: string) { // see if its a filename with a .bin extension if (version.endsWith('.bin')) { - upgradeImportantMessageType_n = 1; // 1 means 3.9 and factory reset required + upgradeImportantMessageType_n = 1; // make it 1, for testing, meaning factory reset required } else if (version.endsWith('.md')) { - upgradeImportantMessageType_n = 0; + upgradeImportantMessageType_n = 0; // use default 0, no message } else { // this is a version string like "3.9.0" - upgradeImportantMessageType_n = 2; + // upgradeImportantMessageType_n = 2; // make it 2, for testing, meaning a major version upgrade + upgradeImportantMessageType_n = 1; // make it 1, for testing, meaning a factory reset is required + } - console.log('upgradeImportantMessageType: ' + upgradeImportantMessageType_n); + console.log('upgradeImportantMessageType: version=' + version + ' type=' + upgradeImportantMessageType_n); return { upgradeImportantMessageType: upgradeImportantMessageType_n }; } @@ -456,17 +458,17 @@ function get_versions() { console.log( 'getVersions: current=' + - THIS_VERSION + - ' stable=' + - LATEST_STABLE_VERSION + - ' (upgradeable=' + - (STABLE_VERSION_IS_UPGRADEABLE ? 'YES' : 'NO') + - ') dev=' + - LATEST_DEV_VERSION + - ' (upgradeable=' + - (DEV_VERSION_IS_UPGRADEABLE ? 'YES' : 'NO') + - ')' + - (MOCK_OFFLINE ? ' [offline]' : '') + THIS_VERSION + + ' stable=' + + LATEST_STABLE_VERSION + + ' (upgradeable=' + + (STABLE_VERSION_IS_UPGRADEABLE ? 'YES' : 'NO') + + ') dev=' + + LATEST_DEV_VERSION + + ' (upgradeable=' + + (DEV_VERSION_IS_UPGRADEABLE ? 'YES' : 'NO') + + ')' + + (MOCK_OFFLINE ? ' [offline]' : '') ); return data; } diff --git a/sonar-project.properties b/sonar-project.properties index 563719c80..d1d03a7d6 100644 --- a/sonar-project.properties +++ b/sonar-project.properties @@ -1,7 +1,7 @@ sonar.organization=emsesp sonar.projectKey=emsesp_EMS-ESP32 sonar.projectName=EMS-ESP32 -sonar.projectVersion=3.8.2 +sonar.projectVersion=3.9.0 sonar.sources=./src sonar.cfamily.compile-commands=bw-output/compile_commands.json sonar.sourceEncoding=UTF-8