diff --git a/.github/workflows/flatpak.yml b/.github/workflows/flatpak.yml index ced6f92b..2a01d78a 100644 --- a/.github/workflows/flatpak.yml +++ b/.github/workflows/flatpak.yml @@ -73,8 +73,8 @@ jobs: gh release download "$TAG" \ --repo "$RELEASE_REPO" \ --dir downloads \ - --pattern "Quadrant_${VERSION}_amd64.deb" \ - --pattern "Quadrant_${VERSION}_arm64.deb" + --pattern "Quadrant-${VERSION}-linux-x64-electron.AppImage" \ + --pattern "Quadrant-${VERSION}-linux-arm64-electron.AppImage" - name: Compute source hashes id: hashes @@ -84,8 +84,8 @@ jobs: set -euo pipefail metainfo_sha="$(sha256sum "$METAINFO_PATH" | awk '{print $1}')" - amd64_sha="$(sha256sum "downloads/Quadrant_${VERSION}_amd64.deb" | awk '{print $1}')" - arm64_sha="$(sha256sum "downloads/Quadrant_${VERSION}_arm64.deb" | awk '{print $1}')" + amd64_sha="$(sha256sum "downloads/Quadrant-${VERSION}-linux-x64-electron.AppImage" | awk '{print $1}')" + arm64_sha="$(sha256sum "downloads/Quadrant-${VERSION}-linux-arm64-electron.AppImage" | awk '{print $1}')" echo "metainfo_sha=$metainfo_sha" >> "$GITHUB_OUTPUT" echo "amd64_sha=$amd64_sha" >> "$GITHUB_OUTPUT" @@ -192,8 +192,8 @@ jobs: set -euo pipefail metainfo_url="https://github.com/${RELEASE_REPO}/raw/${TAG}/${METAINFO_PATH}" - amd64_url="https://github.com/${RELEASE_REPO}/releases/download/${TAG}/Quadrant_${VERSION}_amd64.deb" - arm64_url="https://github.com/${RELEASE_REPO}/releases/download/${TAG}/Quadrant_${VERSION}_arm64.deb" + amd64_url="https://github.com/${RELEASE_REPO}/releases/download/${TAG}/Quadrant-${VERSION}-linux-x64-electron.AppImage" + arm64_url="https://github.com/${RELEASE_REPO}/releases/download/${TAG}/Quadrant-${VERSION}-linux-arm64-electron.AppImage" pr_head="${FLATHUB_HEAD_OWNER}:${FLATHUB_BRANCH}" { diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 500adf6a..4d843ddc 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -37,7 +37,6 @@ jobs: - name: Setup bun uses: oven-sh/setup-bun@v2 - - name: install Rust stable uses: dtolnay/rust-toolchain@stable @@ -71,12 +70,74 @@ jobs: releaseDraft: false prerelease: ${{ !endsWith(github.ref_name, 'stable') }} args: ${{ matrix.args }} - tauriScript: 'bun tauri' + tauriScript: "bun tauri" tagName: ${{github.ref_name}} + publish-electron: + permissions: + contents: write + strategy: + fail-fast: false + matrix: + include: + - platform: "blacksmith-4vcpu-windows-2025" + os: "windows" + arch: "x64" + - platform: "blacksmith-4vcpu-ubuntu-2404" + os: "linux" + arch: "x64" + - platform: "windows-11-arm" + os: "windows" + arch: "arm64" + - platform: "blacksmith-4vcpu-ubuntu-2404-arm" + os: "linux" + arch: "arm64" + runs-on: ${{ matrix.platform }} + steps: + - uses: actions/checkout@v4 + + - name: Setup bun + uses: oven-sh/setup-bun@v2 + + - name: install Rust stable + uses: dtolnay/rust-toolchain@stable + + - name: install dependencies (ubuntu only) + if: matrix.os == 'linux' + run: | + sudo apt update + sudo apt install -y libsecret-1-dev libarchive-tools rpm + + - name: install frontend dependencies (bun) + run: bun install + + - uses: Swatinem/rust-cache@v2 + with: + workspaces: "./src-tauri -> target" + + - name: Package Electron app + env: + ETERNAL_API_TOKEN: ${{ secrets.ETERNAL_API_TOKEN }} + QUADRANT_API_KEY: ${{ secrets.QUADRANT_API_KEY }} + QUADRANT_OAUTH2_CLIENT_ID: ${{ secrets.QUADRANT_OAUTH2_CLIENT_ID }} + QUADRANT_OAUTH2_CLIENT_SECRET: ${{ secrets.QUADRANT_OAUTH2_CLIENT_SECRET }} + QUADRANT_API_BASE_URL: ${{ vars.QUADRANT_API_BASE_URL }} + run: bun run package:electron -- --arch=${{ matrix.arch }} + + - name: Upload Electron assets to release + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + TAG_NAME: ${{ github.ref_name }} + shell: bash + run: | + mapfile -d '' files < <(find dist-electron -maxdepth 1 -type f -print0) + gh release upload "$TAG_NAME" "${files[@]}" --clobber + sync_flathub: name: Sync Flathub - needs: publish-tauri + needs: + - publish-tauri + - publish-electron if: endsWith(github.ref_name, '-stable') || endsWith(github.ref_name, '-flatpak') uses: ./.github/workflows/flatpak.yml secrets: inherit diff --git a/.github/workflows/validate-desktop.yml b/.github/workflows/validate-desktop.yml new file mode 100644 index 00000000..fde1430a --- /dev/null +++ b/.github/workflows/validate-desktop.yml @@ -0,0 +1,52 @@ +name: Validate Desktop + +on: + pull_request: + push: + branches: + - main + - master + - release + +jobs: + validate: + runs-on: ubuntu-24.04 + env: + # quadrant-core still uses compile-time env!() for these desktop-only + # credentials, so CI must provide placeholder values for every Rust build. + QUADRANT_API_KEY: dev + QUADRANT_OAUTH2_CLIENT_ID: dev + QUADRANT_OAUTH2_CLIENT_SECRET: dev + ETERNAL_API_TOKEN: dev + steps: + - uses: actions/checkout@v4 + + - name: Setup bun + uses: oven-sh/setup-bun@v2 + + - name: Install Rust stable + uses: dtolnay/rust-toolchain@stable + + - name: Install Linux desktop dependencies + run: | + sudo apt update + sudo apt install -y libwebkit2gtk-4.1-dev xdg-utils libappindicator3-dev librsvg2-dev patchelf libsecret-1-dev libarchive-tools rpm + + - name: Install frontend dependencies + run: bun install + + - uses: Swatinem/rust-cache@v2 + with: + workspaces: "./src-tauri -> target" + + - name: Build shared renderer + run: bun run build + + - name: Build native addon + run: bun run build:napi + + - name: Build Electron shell + run: node scripts/build-electron.mjs + + - name: Build Tauri shell + run: bun tauri build --no-bundle diff --git a/.gitignore b/.gitignore index 25a1af32..b90e8dab 100644 --- a/.gitignore +++ b/.gitignore @@ -9,6 +9,7 @@ lerna-debug.log* node_modules dist +dist-electron dist-ssr *.local @@ -39,3 +40,5 @@ src-tauri/versoview target/ AppxContent AppxBundles +electron/runtime-config.generated.json +packages/quadrant-node/native/ diff --git a/.vscode/settings.json b/.vscode/settings.json index affeff70..0a999877 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,6 +1,4 @@ { - "rust-analyzer.cargo.cfgs": [ - "debug_assertions" - ], - "typescript.tsdk": "node_modules\\typescript\\lib" -} \ No newline at end of file + "rust-analyzer.cargo.cfgs": ["debug_assertions"], + "typescript.tsdk": "node_modules\\typescript\\lib" +} diff --git a/AppxManifest.xml b/AppxManifest.xml index 289747bc..15d47635 100644 --- a/AppxManifest.xml +++ b/AppxManifest.xml @@ -8,7 +8,7 @@ xmlns:desktop="http://schemas.microsoft.com/appx/manifest/desktop/windows10" xmlns:rescap="http://schemas.microsoft.com/appx/manifest/foundation/windows10/restrictedcapabilities" IgnorableNamespaces="uap uap3 uap10 desktop rescap"> - QuadrantMC diff --git a/DEVELOP.md b/DEVELOP.md index 476a3c72..0d60d695 100644 --- a/DEVELOP.md +++ b/DEVELOP.md @@ -4,4 +4,10 @@ You can develop the Quadrant Next client by: - Installing the needed dependencies for [Tauri](https://tauri.app/start/prerequisites/) and [Rust](https://www.rust-lang.org/). If you're on Linux, you'll also need libsecret-1-dev. - Then install bun and run `bun install` in the root directory of the project. -- After that in order to run the app without any of the proprietary features, you can run `bun tauri dev -- -- --no-default-features`. +- Renderer-only development remains available through `bun run dev`. +- Tauri development is available through `bun run dev:tauri`. +- Electron development is available through `bun run dev:electron`. +- To build the shared renderer only, run `bun run build`. +- To build the native Electron addon explicitly, run `bun run build:napi`. +- To package the Electron app, run `bun run package:electron`. +- To build the Tauri app without any of the proprietary features, run `bun tauri dev -- -- --no-default-features`. diff --git a/README.md b/README.md index ae9708bd..f38e7a75 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,13 @@ > An easy way to manage your modpacks, written in React and Rust. +## Desktop Runtimes + +Quadrant supports two desktop runtimes, they share the frontend and backend logic, but differ in technicalities: + +- `Tauri`: the original Quadrant experience. +- `Electron`: the Node/Electron shell in `electron/`, backed by the Quadrant N-API bindings, should work better on Linux, because it replaces WebKit with CEF. + ### Installation guide: ##### The app is available on these stores: diff --git a/bun.lock b/bun.lock index 7b5ab259..f9a30a92 100644 --- a/bun.lock +++ b/bun.lock @@ -1,45 +1,51 @@ { "lockfileVersion": 1, - "configVersion": 0, + "configVersion": 1, "workspaces": { "": { "name": "quadrant-next", "dependencies": { "@fabianlars/tauri-plugin-oauth": "^2.0.0", "@headlessui/react": "^2.2.9", + "@quadrant/quadrant-node": "workspace:*", "@tauri-apps/api": "^2.10.1", "@tauri-apps/plugin-autostart": "~2.5.1", "@tauri-apps/plugin-cli": "~2.4.1", "@tauri-apps/plugin-clipboard-manager": "2.3.2", - "@tauri-apps/plugin-deep-link": "~2.4.7", - "@tauri-apps/plugin-dialog": "~2.6.0", - "@tauri-apps/plugin-fs": "~2.4.5", - "@tauri-apps/plugin-http": "~2.5.7", + "@tauri-apps/plugin-deep-link": "~2.4.8", + "@tauri-apps/plugin-dialog": "~2.7.0", + "@tauri-apps/plugin-fs": "~2.5.0", + "@tauri-apps/plugin-http": "~2.5.8", "@tauri-apps/plugin-notification": "2.3.3", "@tauri-apps/plugin-opener": "2.5.3", "@tauri-apps/plugin-os": "~2.3.2", "@tauri-apps/plugin-store": "2.4.2", - "@tauri-apps/plugin-updater": "2.10.0", - "i18next": "26.0.1", + "@tauri-apps/plugin-updater": "2.10.1", + "i18next": "26.0.3", "motion": "12.38.0", "react": "19.2.4", "react-dom": "19.2.4", - "react-i18next": "^17.0.1", + "react-i18next": "^17.0.2", "react-icons": "^5.6.0", }, "devDependencies": { "@choochmeque/tauri-windows-bundle": "^0.1.17", - "@eslint/css": "^1.0.0", + "@eslint/css": "^1.1.0", "@eslint/js": "^10.0.1", "@eslint/json": "^1.2.0", "@tailwindcss/postcss": "4.2.2", "@tauri-apps/cli": "^2.10.1", + "@types/node": "^25.5.2", "@types/react": "^19.2.14", "@types/react-dom": "^19.2.3", "@vitejs/plugin-react": "^6.0.1", "autoprefixer": "^10.4.27", "babel-plugin-react-compiler": "1.0.0", - "eslint": "^10.1.0", + "chokidar": "^5.0.0", + "electron": "^41.1.1", + "electron-builder": "^26.8.1", + "electron-updater": "^6.8.3", + "eslint": "^10.2.0", "eslint-config-prettier": "^10.1.8", "eslint-plugin-import": "^2.32.0", "eslint-plugin-jsx-a11y": "^6.10.2", @@ -52,70 +58,95 @@ "prettier-eslint": "^16.4.2", "tailwindcss": "4.2.2", "typescript": "6.0.2", - "typescript-eslint": "^8.57.2", + "typescript-eslint": "^8.58.0", "vite": "^8.0.3", }, }, + "packages/quadrant-node": { + "name": "@quadrant/quadrant-node", + "version": "26.4.0", + }, }, "trustedDependencies": [ + "electron", "@tailwindcss/oxide", ], "packages": { + "7zip-bin": ["7zip-bin@5.2.0", "", {}, "sha512-ukTPVhqG4jNzMro2qA9HSCSSVJN3aN7tlb+hfqYCt3ER0yWroeA2VR38MNrOHLQ/cVj+DaIMad0kFCtWWowh/A=="], + "@alloc/quick-lru": ["@alloc/quick-lru@5.2.0", "", {}, "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw=="], - "@babel/code-frame": ["@babel/code-frame@7.27.1", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.27.1", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg=="], + "@babel/code-frame": ["@babel/code-frame@7.29.0", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.28.5", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw=="], - "@babel/compat-data": ["@babel/compat-data@7.28.0", "", {}, "sha512-60X7qkglvrap8mn1lh2ebxXdZYtUcpd7gsmy9kLaBJ4i/WdY8PqTSdxyA8qraikqKQK5C1KRBKXqznrVapyNaw=="], + "@babel/compat-data": ["@babel/compat-data@7.29.0", "", {}, "sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg=="], - "@babel/core": ["@babel/core@7.28.4", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.3", "@babel/helper-compilation-targets": "^7.27.2", "@babel/helper-module-transforms": "^7.28.3", "@babel/helpers": "^7.28.4", "@babel/parser": "^7.28.4", "@babel/template": "^7.27.2", "@babel/traverse": "^7.28.4", "@babel/types": "^7.28.4", "@jridgewell/remapping": "^2.3.5", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", "json5": "^2.2.3", "semver": "^6.3.1" } }, "sha512-2BCOP7TN8M+gVDj7/ht3hsaO/B/n5oDbiAyyvnRlNOs+u1o+JWNYTQrmpuNp1/Wq2gcFrI01JAW+paEKDMx/CA=="], + "@babel/core": ["@babel/core@7.29.0", "", { "dependencies": { "@babel/code-frame": "^7.29.0", "@babel/generator": "^7.29.0", "@babel/helper-compilation-targets": "^7.28.6", "@babel/helper-module-transforms": "^7.28.6", "@babel/helpers": "^7.28.6", "@babel/parser": "^7.29.0", "@babel/template": "^7.28.6", "@babel/traverse": "^7.29.0", "@babel/types": "^7.29.0", "@jridgewell/remapping": "^2.3.5", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", "json5": "^2.2.3", "semver": "^6.3.1" } }, "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA=="], - "@babel/generator": ["@babel/generator@7.28.3", "", { "dependencies": { "@babel/parser": "^7.28.3", "@babel/types": "^7.28.2", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-3lSpxGgvnmZznmBkCRnVREPUFJv2wrv9iAoFDvADJc0ypmdOxdUtcLeBgBJ6zE0PMeTKnxeQzyk0xTBq4Ep7zw=="], + "@babel/generator": ["@babel/generator@7.29.1", "", { "dependencies": { "@babel/parser": "^7.29.0", "@babel/types": "^7.29.0", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw=="], - "@babel/helper-compilation-targets": ["@babel/helper-compilation-targets@7.27.2", "", { "dependencies": { "@babel/compat-data": "^7.27.2", "@babel/helper-validator-option": "^7.27.1", "browserslist": "^4.24.0", "lru-cache": "^5.1.1", "semver": "^6.3.1" } }, "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ=="], + "@babel/helper-compilation-targets": ["@babel/helper-compilation-targets@7.28.6", "", { "dependencies": { "@babel/compat-data": "^7.28.6", "@babel/helper-validator-option": "^7.27.1", "browserslist": "^4.24.0", "lru-cache": "^5.1.1", "semver": "^6.3.1" } }, "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA=="], "@babel/helper-globals": ["@babel/helper-globals@7.28.0", "", {}, "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw=="], - "@babel/helper-module-imports": ["@babel/helper-module-imports@7.27.1", "", { "dependencies": { "@babel/traverse": "^7.27.1", "@babel/types": "^7.27.1" } }, "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w=="], + "@babel/helper-module-imports": ["@babel/helper-module-imports@7.28.6", "", { "dependencies": { "@babel/traverse": "^7.28.6", "@babel/types": "^7.28.6" } }, "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw=="], - "@babel/helper-module-transforms": ["@babel/helper-module-transforms@7.28.3", "", { "dependencies": { "@babel/helper-module-imports": "^7.27.1", "@babel/helper-validator-identifier": "^7.27.1", "@babel/traverse": "^7.28.3" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw=="], + "@babel/helper-module-transforms": ["@babel/helper-module-transforms@7.28.6", "", { "dependencies": { "@babel/helper-module-imports": "^7.28.6", "@babel/helper-validator-identifier": "^7.28.5", "@babel/traverse": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA=="], "@babel/helper-string-parser": ["@babel/helper-string-parser@7.27.1", "", {}, "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA=="], - "@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.27.1", "", {}, "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow=="], + "@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.28.5", "", {}, "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q=="], "@babel/helper-validator-option": ["@babel/helper-validator-option@7.27.1", "", {}, "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg=="], - "@babel/helpers": ["@babel/helpers@7.28.4", "", { "dependencies": { "@babel/template": "^7.27.2", "@babel/types": "^7.28.4" } }, "sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w=="], + "@babel/helpers": ["@babel/helpers@7.29.2", "", { "dependencies": { "@babel/template": "^7.28.6", "@babel/types": "^7.29.0" } }, "sha512-HoGuUs4sCZNezVEKdVcwqmZN8GoHirLUcLaYVNBK2J0DadGtdcqgr3BCbvH8+XUo4NGjNl3VOtSjEKNzqfFgKw=="], - "@babel/parser": ["@babel/parser@7.28.4", "", { "dependencies": { "@babel/types": "^7.28.4" }, "bin": "./bin/babel-parser.js" }, "sha512-yZbBqeM6TkpP9du/I2pUZnJsRMGGvOuIrhjzC1AwHwW+6he4mni6Bp/m8ijn0iOuZuPI2BfkCoSRunpyjnrQKg=="], + "@babel/parser": ["@babel/parser@7.29.2", "", { "dependencies": { "@babel/types": "^7.29.0" }, "bin": "./bin/babel-parser.js" }, "sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA=="], "@babel/runtime": ["@babel/runtime@7.29.2", "", {}, "sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g=="], - "@babel/template": ["@babel/template@7.27.2", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/parser": "^7.27.2", "@babel/types": "^7.27.1" } }, "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw=="], + "@babel/template": ["@babel/template@7.28.6", "", { "dependencies": { "@babel/code-frame": "^7.28.6", "@babel/parser": "^7.28.6", "@babel/types": "^7.28.6" } }, "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ=="], - "@babel/traverse": ["@babel/traverse@7.28.4", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.3", "@babel/helper-globals": "^7.28.0", "@babel/parser": "^7.28.4", "@babel/template": "^7.27.2", "@babel/types": "^7.28.4", "debug": "^4.3.1" } }, "sha512-YEzuboP2qvQavAcjgQNVgsvHIDv6ZpwXvcvjmyySP2DIMuByS/6ioU5G9pYrWHM6T2YDfc7xga9iNzYOs12CFQ=="], + "@babel/traverse": ["@babel/traverse@7.29.0", "", { "dependencies": { "@babel/code-frame": "^7.29.0", "@babel/generator": "^7.29.0", "@babel/helper-globals": "^7.28.0", "@babel/parser": "^7.29.0", "@babel/template": "^7.28.6", "@babel/types": "^7.29.0", "debug": "^4.3.1" } }, "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA=="], - "@babel/types": ["@babel/types@7.28.4", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.27.1" } }, "sha512-bkFqkLhh3pMBUQQkpVgWDWq/lqzc2678eUyDlTBhRqhCHFguYYGM0Efga7tYk4TogG/3x0EEl66/OQ+WGbWB/Q=="], + "@babel/types": ["@babel/types@7.29.0", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A=="], "@choochmeque/tauri-windows-bundle": ["@choochmeque/tauri-windows-bundle@0.1.17", "", { "dependencies": { "commander": "^14.0.3", "glob": "^13.0.6", "image-js": "^1.4.0" }, "bin": { "tauri-windows-bundle": "dist/cli.js" } }, "sha512-fXlizNfkNquW2kdnXdHA/nqMljK4cE/PuT6HA0Wde4InUSJU0s5z5PtaQhkcAp76MaGCIWOIhFZetZX2YVaoWg=="], - "@emnapi/core": ["@emnapi/core@1.8.1", "", { "dependencies": { "@emnapi/wasi-threads": "1.1.0", "tslib": "^2.4.0" } }, "sha512-AvT9QFpxK0Zd8J0jopedNm+w/2fIzvtPKPjqyw9jwvBaReTTqPBk9Hixaz7KbjimP+QNz605/XnjFcDAL2pqBg=="], + "@develar/schema-utils": ["@develar/schema-utils@2.6.5", "", { "dependencies": { "ajv": "^6.12.0", "ajv-keywords": "^3.4.1" } }, "sha512-0cp4PsWQ/9avqTVMCtZ+GirikIA36ikvjtHweU4/j8yLtgObI0+JUPhYFScgwlteveGB1rt3Cm8UhN04XayDig=="], + + "@electron/asar": ["@electron/asar@3.4.1", "", { "dependencies": { "commander": "^5.0.0", "glob": "^7.1.6", "minimatch": "^3.0.4" }, "bin": { "asar": "bin/asar.js" } }, "sha512-i4/rNPRS84t0vSRa2HorerGRXWyF4vThfHesw0dmcWHp+cspK743UanA0suA5Q5y8kzY2y6YKrvbIUn69BCAiA=="], + + "@electron/fuses": ["@electron/fuses@1.8.0", "", { "dependencies": { "chalk": "^4.1.1", "fs-extra": "^9.0.1", "minimist": "^1.2.5" }, "bin": { "electron-fuses": "dist/bin.js" } }, "sha512-zx0EIq78WlY/lBb1uXlziZmDZI4ubcCXIMJ4uGjXzZW0nS19TjSPeXPAjzzTmKQlJUZm0SbmZhPKP7tuQ1SsEw=="], + + "@electron/get": ["@electron/get@2.0.3", "", { "dependencies": { "debug": "^4.1.1", "env-paths": "^2.2.0", "fs-extra": "^8.1.0", "got": "^11.8.5", "progress": "^2.0.3", "semver": "^6.2.0", "sumchecker": "^3.0.1" }, "optionalDependencies": { "global-agent": "^3.0.0" } }, "sha512-Qkzpg2s9GnVV2I2BjRksUi43U5e6+zaQMcjoJy0C+C5oxaKl+fmckGDQFtRpZpZV0NQekuZZ+tGz7EA9TVnQtQ=="], + + "@electron/notarize": ["@electron/notarize@2.5.0", "", { "dependencies": { "debug": "^4.1.1", "fs-extra": "^9.0.1", "promise-retry": "^2.0.1" } }, "sha512-jNT8nwH1f9X5GEITXaQ8IF/KdskvIkOFfB2CvwumsveVidzpSc+mvhhTMdAGSYF3O+Nq49lJ7y+ssODRXu06+A=="], + + "@electron/osx-sign": ["@electron/osx-sign@1.3.3", "", { "dependencies": { "compare-version": "^0.1.2", "debug": "^4.3.4", "fs-extra": "^10.0.0", "isbinaryfile": "^4.0.8", "minimist": "^1.2.6", "plist": "^3.0.5" }, "bin": { "electron-osx-flat": "bin/electron-osx-flat.js", "electron-osx-sign": "bin/electron-osx-sign.js" } }, "sha512-KZ8mhXvWv2rIEgMbWZ4y33bDHyUKMXnx4M0sTyPNK/vcB81ImdeY9Ggdqy0SWbMDgmbqyQ+phgejh6V3R2QuSg=="], + + "@electron/rebuild": ["@electron/rebuild@4.0.3", "", { "dependencies": { "@malept/cross-spawn-promise": "^2.0.0", "debug": "^4.1.1", "detect-libc": "^2.0.1", "got": "^11.7.0", "graceful-fs": "^4.2.11", "node-abi": "^4.2.0", "node-api-version": "^0.2.1", "node-gyp": "^11.2.0", "ora": "^5.1.0", "read-binary-file-arch": "^1.0.6", "semver": "^7.3.5", "tar": "^7.5.6", "yargs": "^17.0.1" }, "bin": { "electron-rebuild": "lib/cli.js" } }, "sha512-u9vpTHRMkOYCs/1FLiSVAFZ7FbjsXK+bQuzviJZa+lG7BHZl1nz52/IcGvwa3sk80/fc3llutBkbCq10Vh8WQA=="], + + "@electron/universal": ["@electron/universal@2.0.3", "", { "dependencies": { "@electron/asar": "^3.3.1", "@malept/cross-spawn-promise": "^2.0.0", "debug": "^4.3.1", "dir-compare": "^4.2.0", "fs-extra": "^11.1.1", "minimatch": "^9.0.3", "plist": "^3.1.0" } }, "sha512-Wn9sPYIVFRFl5HmwMJkARCCf7rqK/EurkfQ/rJZ14mHP3iYTjZSIOSVonEAnhWeAXwtw7zOekGRlc6yTtZ0t+g=="], - "@emnapi/runtime": ["@emnapi/runtime@1.8.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-mehfKSMWjjNol8659Z8KxEMrdSJDDot5SXMq00dM8BN4o+CLNXQ0xH2V7EchNHV4RmbZLmmPdEaXZc5H2FXmDg=="], + "@electron/windows-sign": ["@electron/windows-sign@1.2.2", "", { "dependencies": { "cross-dirname": "^0.1.0", "debug": "^4.3.4", "fs-extra": "^11.1.1", "minimist": "^1.2.8", "postject": "^1.0.0-alpha.6" }, "bin": { "electron-windows-sign": "bin/electron-windows-sign.js" } }, "sha512-dfZeox66AvdPtb2lD8OsIIQh12Tp0GNCRUDfBHIKGpbmopZto2/A8nSpYYLoedPIHpqkeblZ/k8OV0Gy7PYuyQ=="], - "@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.1.0", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ=="], + "@emnapi/core": ["@emnapi/core@1.9.2", "", { "dependencies": { "@emnapi/wasi-threads": "1.2.1", "tslib": "^2.4.0" } }, "sha512-UC+ZhH3XtczQYfOlu3lNEkdW/p4dsJ1r/bP7H8+rhao3TTTMO1ATq/4DdIi23XuGoFY+Cz0JmCbdVl0hz9jZcA=="], + + "@emnapi/runtime": ["@emnapi/runtime@1.9.2", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-3U4+MIWHImeyu1wnmVygh5WlgfYDtyf0k8AbLhMFxOipihf6nrWC4syIm/SwEeec0mNSafiiNnMJwbza/Is6Lw=="], + + "@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.2.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w=="], "@eslint-community/eslint-utils": ["@eslint-community/eslint-utils@4.9.1", "", { "dependencies": { "eslint-visitor-keys": "^3.4.3" }, "peerDependencies": { "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" } }, "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ=="], "@eslint-community/regexpp": ["@eslint-community/regexpp@4.12.2", "", {}, "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew=="], - "@eslint/config-array": ["@eslint/config-array@0.23.3", "", { "dependencies": { "@eslint/object-schema": "^3.0.3", "debug": "^4.3.1", "minimatch": "^10.2.4" } }, "sha512-j+eEWmB6YYLwcNOdlwQ6L2OsptI/LO6lNBuLIqe5R7RetD658HLoF+Mn7LzYmAWWNNzdC6cqP+L6r8ujeYXWLw=="], + "@eslint/config-array": ["@eslint/config-array@0.23.4", "", { "dependencies": { "@eslint/object-schema": "^3.0.4", "debug": "^4.3.1", "minimatch": "^10.2.4" } }, "sha512-lf19F24LSMfF8weXvW5QEtnLqW70u7kgit5e9PSx0MsHAFclGd1T9ynvWEMDT1w5J4Qt54tomGeAhdoAku1Xow=="], - "@eslint/config-helpers": ["@eslint/config-helpers@0.5.3", "", { "dependencies": { "@eslint/core": "^1.1.1" } }, "sha512-lzGN0onllOZCGroKJmRwY6QcEHxbjBw1gwB8SgRSqK8YbbtEXMvKynsXc3553ckIEBxsbMBU7oOZXKIPGZNeZw=="], + "@eslint/config-helpers": ["@eslint/config-helpers@0.5.4", "", { "dependencies": { "@eslint/core": "^1.2.0" } }, "sha512-jJhqiY3wPMlWWO3370M86CPJ7pt8GmEwSLglMfQhjXal07RCvhmU0as4IuUEW5SJeunfItiEetHmSxCCe9lDBg=="], - "@eslint/core": ["@eslint/core@1.1.1", "", { "dependencies": { "@types/json-schema": "^7.0.15" } }, "sha512-QUPblTtE51/7/Zhfv8BDwO0qkkzQL7P/aWWbqcf4xWLEYn1oKjdO0gglQBB4GAsu7u6wjijbCmzsUTy6mnk6oQ=="], + "@eslint/core": ["@eslint/core@1.2.0", "", { "dependencies": { "@types/json-schema": "^7.0.15" } }, "sha512-8FTGbNzTvmSlc4cZBaShkC6YvFMG0riksYWRFKXztqVdXaQbcZLXlFbSpC05s70sGEsXAw0qwhx69JiW7hQS7A=="], - "@eslint/css": ["@eslint/css@1.0.0", "", { "dependencies": { "@eslint/core": "^1.1.1", "@eslint/css-tree": "^3.6.9", "@eslint/plugin-kit": "^0.6.1" } }, "sha512-2MjyL517F1wrLrmmfaFntKKiImudiXW6Dd80oE7VRIRu64bT5YieRs+WPgdZy9m8izs/tSUqi3j2mBnjUrqJfA=="], + "@eslint/css": ["@eslint/css@1.1.0", "", { "dependencies": { "@eslint/core": "^1.1.1", "@eslint/css-tree": "^3.6.9", "@eslint/plugin-kit": "^0.6.1" } }, "sha512-sNwfLcU3nKXv/v2YglqujwMU4Iv3BDhxldNUd/2FckVab0zdvc9pPlKWxjR6Ap/EU+Y8Pdu853iwvcUpemRhRw=="], "@eslint/css-tree": ["@eslint/css-tree@3.6.9", "", { "dependencies": { "mdn-data": "2.23.0", "source-map-js": "^1.0.1" } }, "sha512-3D5/OHibNEGk+wKwNwMbz63NMf367EoR4mVNNpxddCHKEb2Nez7z62J2U6YjtErSsZDoY0CsccmoUpdEbkogNA=="], @@ -125,27 +156,27 @@ "@eslint/json": ["@eslint/json@1.2.0", "", { "dependencies": { "@eslint/core": "^1.1.1", "@eslint/plugin-kit": "^0.6.1", "@humanwhocodes/momoa": "^3.3.10", "natural-compare": "^1.4.0" } }, "sha512-CEFEyNgvzu8zn5QwVYDg3FaG+ZKUeUsNYitFpMYJAqoAlnw68EQgNbUfheSmexZr4n0wZPrAkPLuvsLaXO6wRw=="], - "@eslint/object-schema": ["@eslint/object-schema@3.0.3", "", {}, "sha512-iM869Pugn9Nsxbh/YHRqYiqd23AmIbxJOcpUMOuWCVNdoQJ5ZtwL6h3t0bcZzJUlC3Dq9jCFCESBZnX0GTv7iQ=="], + "@eslint/object-schema": ["@eslint/object-schema@3.0.4", "", {}, "sha512-55lO/7+Yp0ISKRP0PsPtNTeNGapXaO085aELZmWCVc5SH3jfrqpuU6YgOdIxMS99ZHkQN1cXKE+cdIqwww9ptw=="], "@eslint/plugin-kit": ["@eslint/plugin-kit@0.6.1", "", { "dependencies": { "@eslint/core": "^1.1.1", "levn": "^0.4.1" } }, "sha512-iH1B076HoAshH1mLpHMgwdGeTs0CYwL0SPMkGuSebZrwBp16v415e9NZXg2jtrqPVQjf6IANe2Vtlr5KswtcZQ=="], "@fabianlars/tauri-plugin-oauth": ["@fabianlars/tauri-plugin-oauth@2.0.0", "", { "dependencies": { "@tauri-apps/api": "^2.0.3" } }, "sha512-I1s08ZXrsFuYfNWusAcpLyiCfr5TCvaBrRuKfTG+XQrcaqnAcwjdWH0U5J9QWuMDLwCUMnVxdobtMJzPR8raxQ=="], - "@floating-ui/core": ["@floating-ui/core@1.6.9", "", { "dependencies": { "@floating-ui/utils": "^0.2.9" } }, "sha512-uMXCuQ3BItDUbAMhIXw7UPXRfAlOAvZzdK9BWpE60MCn+Svt3aLn9jsPTi/WNGlRUu2uI0v5S7JiIUsbsvh3fw=="], + "@floating-ui/core": ["@floating-ui/core@1.7.5", "", { "dependencies": { "@floating-ui/utils": "^0.2.11" } }, "sha512-1Ih4WTWyw0+lKyFMcBHGbb5U5FtuHJuujoyyr5zTaWS5EYMeT6Jb2AuDeftsCsEuchO+mM2ij5+q9crhydzLhQ=="], - "@floating-ui/dom": ["@floating-ui/dom@1.6.13", "", { "dependencies": { "@floating-ui/core": "^1.6.0", "@floating-ui/utils": "^0.2.9" } }, "sha512-umqzocjDgNRGTuO7Q8CU32dkHkECqI8ZdMZ5Swb6QAM0t5rnlrN3lGo1hdpscRd3WS8T6DKYK4ephgIH9iRh3w=="], + "@floating-ui/dom": ["@floating-ui/dom@1.7.6", "", { "dependencies": { "@floating-ui/core": "^1.7.5", "@floating-ui/utils": "^0.2.11" } }, "sha512-9gZSAI5XM36880PPMm//9dfiEngYoC6Am2izES1FF406YFsjvyBMmeJ2g4SAju3xWwtuynNRFL2s9hgxpLI5SQ=="], "@floating-ui/react": ["@floating-ui/react@0.26.28", "", { "dependencies": { "@floating-ui/react-dom": "^2.1.2", "@floating-ui/utils": "^0.2.8", "tabbable": "^6.0.0" }, "peerDependencies": { "react": ">=16.8.0", "react-dom": ">=16.8.0" } }, "sha512-yORQuuAtVpiRjpMhdc0wJj06b9JFjrYF4qp96j++v2NBpbi6SEGF7donUJ3TMieerQ6qVkAv1tgr7L4r5roTqw=="], - "@floating-ui/react-dom": ["@floating-ui/react-dom@2.1.2", "", { "dependencies": { "@floating-ui/dom": "^1.0.0" }, "peerDependencies": { "react": ">=16.8.0", "react-dom": ">=16.8.0" } }, "sha512-06okr5cgPzMNBy+Ycse2A6udMi4bqwW/zgBF/rwjcNqWkyr82Mcg8b0vjX8OJpZFy/FKjJmw6wV7t44kK6kW7A=="], + "@floating-ui/react-dom": ["@floating-ui/react-dom@2.1.8", "", { "dependencies": { "@floating-ui/dom": "^1.7.6" }, "peerDependencies": { "react": ">=16.8.0", "react-dom": ">=16.8.0" } }, "sha512-cC52bHwM/n/CxS87FH0yWdngEZrjdtLW/qVruo68qg+prK7ZQ4YGdut2GyDVpoGeAYe/h899rVeOVm6Oi40k2A=="], - "@floating-ui/utils": ["@floating-ui/utils@0.2.9", "", {}, "sha512-MDWhGtE+eHw5JW7lq4qhc5yRLS11ERl1c7Z6Xd0a58DozHES6EnNNwUWbMiG4J9Cgj053Bhk8zvlhFYKVhULwg=="], + "@floating-ui/utils": ["@floating-ui/utils@0.2.11", "", {}, "sha512-RiB/yIh78pcIxl6lLMG0CgBXAZ2Y0eVHqMPYugu+9U0AeT6YBeiJpf7lbdJNIugFP5SIjwNRgo4DhR1Qxi26Gg=="], "@headlessui/react": ["@headlessui/react@2.2.9", "", { "dependencies": { "@floating-ui/react": "^0.26.16", "@react-aria/focus": "^3.20.2", "@react-aria/interactions": "^3.25.0", "@tanstack/react-virtual": "^3.13.9", "use-sync-external-store": "^1.5.0" }, "peerDependencies": { "react": "^18 || ^19 || ^19.0.0-rc", "react-dom": "^18 || ^19 || ^19.0.0-rc" } }, "sha512-Mb+Un58gwBn0/yWZfyrCh0TJyurtT+dETj7YHleylHk5od3dv2XqETPGWMyQ5/7sYN7oWdyM1u9MvC0OC8UmzQ=="], "@humanfs/core": ["@humanfs/core@0.19.1", "", {}, "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA=="], - "@humanfs/node": ["@humanfs/node@0.16.6", "", { "dependencies": { "@humanfs/core": "^0.19.1", "@humanwhocodes/retry": "^0.3.0" } }, "sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw=="], + "@humanfs/node": ["@humanfs/node@0.16.7", "", { "dependencies": { "@humanfs/core": "^0.19.1", "@humanwhocodes/retry": "^0.4.0" } }, "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ=="], "@humanwhocodes/config-array": ["@humanwhocodes/config-array@0.13.0", "", { "dependencies": { "@humanwhocodes/object-schema": "^2.0.3", "debug": "^4.3.1", "minimatch": "^3.0.5" } }, "sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw=="], @@ -157,9 +188,13 @@ "@humanwhocodes/retry": ["@humanwhocodes/retry@0.4.3", "", {}, "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ=="], + "@isaacs/cliui": ["@isaacs/cliui@8.0.2", "", { "dependencies": { "string-width": "^5.1.2", "string-width-cjs": "npm:string-width@^4.2.0", "strip-ansi": "^7.0.1", "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", "wrap-ansi": "^8.1.0", "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" } }, "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA=="], + + "@isaacs/fs-minipass": ["@isaacs/fs-minipass@4.0.1", "", { "dependencies": { "minipass": "^7.0.4" } }, "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w=="], + "@jest/schemas": ["@jest/schemas@29.6.3", "", { "dependencies": { "@sinclair/typebox": "^0.27.8" } }, "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA=="], - "@jridgewell/gen-mapping": ["@jridgewell/gen-mapping@0.3.12", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-OuLGC46TjB5BbN1dH8JULVVZY4WTdkF7tV9Ys6wLL1rubZnCMstOhNHueU5bLCrnRuDhKPDM4g6sw4Bel5Gzqg=="], + "@jridgewell/gen-mapping": ["@jridgewell/gen-mapping@0.3.13", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA=="], "@jridgewell/remapping": ["@jridgewell/remapping@2.3.5", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ=="], @@ -167,9 +202,13 @@ "@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.5", "", {}, "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og=="], - "@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.29", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-uw6guiW/gcAGPDhLmd77/6lW8QLeiV5RUTsAX46Db6oLhGaVj4lhnPwb184s1bkc8kdVg/+h988dro8GRDpmYQ=="], + "@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.31", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw=="], + + "@malept/cross-spawn-promise": ["@malept/cross-spawn-promise@2.0.0", "", { "dependencies": { "cross-spawn": "^7.0.1" } }, "sha512-1DpKU0Z5ThltBwjNySMC14g0CkbyhCaz9FkhxqNsZI6uAPJXFS8cMXlBKo26FJ8ZuW6S9GCMcR9IO5k2X5/9Fg=="], - "@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@1.1.1", "", { "dependencies": { "@emnapi/core": "^1.7.1", "@emnapi/runtime": "^1.7.1", "@tybys/wasm-util": "^0.10.1" } }, "sha512-p64ah1M1ld8xjWv3qbvFwHiFVWrq1yFvV4f7w+mzaqiR4IlSgkqhcRdHwsGgomwzBH51sRY4NEowLxnaBjcW/A=="], + "@malept/flatpak-bundler": ["@malept/flatpak-bundler@0.4.0", "", { "dependencies": { "debug": "^4.1.1", "fs-extra": "^9.0.0", "lodash": "^4.17.15", "tmp-promise": "^3.0.2" } }, "sha512-9QOtNffcOF/c1seMCDnjckb3R9WHcG34tky+FHpNKKCW0wc/scYLwMtO+ptyGUfMW0/b/n4qRiALlaFHc9Oj7Q=="], + + "@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@1.1.2", "", { "dependencies": { "@tybys/wasm-util": "^0.10.1" }, "peerDependencies": { "@emnapi/core": "^1.7.1", "@emnapi/runtime": "^1.7.1" } }, "sha512-sNXv5oLJ7ob93xkZ1XnxisYhGYXfaG9f65/ZgYuAu3qt7b3NadcOEhLvx28hv31PgX8SZJRYrAIPQilQmFpLVw=="], "@nodelib/fs.scandir": ["@nodelib/fs.scandir@2.1.5", "", { "dependencies": { "@nodelib/fs.stat": "2.0.5", "run-parallel": "^1.1.9" } }, "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g=="], @@ -177,23 +216,31 @@ "@nodelib/fs.walk": ["@nodelib/fs.walk@1.2.8", "", { "dependencies": { "@nodelib/fs.scandir": "2.1.5", "fastq": "^1.6.0" } }, "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg=="], + "@npmcli/agent": ["@npmcli/agent@3.0.0", "", { "dependencies": { "agent-base": "^7.1.0", "http-proxy-agent": "^7.0.0", "https-proxy-agent": "^7.0.1", "lru-cache": "^10.0.1", "socks-proxy-agent": "^8.0.3" } }, "sha512-S79NdEgDQd/NGCay6TCoVzXSj74skRZIKJcpJjC5lOq34SZzyI6MqtiiWoiVWoVrTcGjNeC4ipbh1VIHlpfF5Q=="], + + "@npmcli/fs": ["@npmcli/fs@4.0.0", "", { "dependencies": { "semver": "^7.3.5" } }, "sha512-/xGlezI6xfGO9NwuJlnwz/K14qD1kCSAGtacBHnGzeAIuJGazcp45KP5NuyARXoKb7cwulAGWVsbeSxdG/cb0Q=="], + "@oxc-project/types": ["@oxc-project/types@0.122.0", "", {}, "sha512-oLAl5kBpV4w69UtFZ9xqcmTi+GENWOcPF7FCrczTiBbmC0ibXxCwyvZGbO39rCVEuLGAZM84DH0pUIyyv/YJzA=="], + "@pkgjs/parseargs": ["@pkgjs/parseargs@0.11.0", "", {}, "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg=="], + "@pkgr/core": ["@pkgr/core@0.2.9", "", {}, "sha512-QNqXyfVS2wm9hweSYD2O7F0G06uurj9kZ96TRQE5Y9hU7+tgdZwIkbAKc5Ocy1HxEY2kuDQa6cQ1WRs/O5LFKA=="], - "@react-aria/focus": ["@react-aria/focus@3.20.3", "", { "dependencies": { "@react-aria/interactions": "^3.25.1", "@react-aria/utils": "^3.29.0", "@react-types/shared": "^3.29.1", "@swc/helpers": "^0.5.0", "clsx": "^2.0.0" }, "peerDependencies": { "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" } }, "sha512-rR5uZUMSY4xLHmpK/I8bP1V6vUNHFo33gTvrvNUsAKKqvMfa7R2nu5A6v97dr5g6tVH6xzpdkPsOJCWh90H2cw=="], + "@quadrant/quadrant-node": ["@quadrant/quadrant-node@workspace:packages/quadrant-node"], - "@react-aria/interactions": ["@react-aria/interactions@3.25.1", "", { "dependencies": { "@react-aria/ssr": "^3.9.8", "@react-aria/utils": "^3.29.0", "@react-stately/flags": "^3.1.1", "@react-types/shared": "^3.29.1", "@swc/helpers": "^0.5.0" }, "peerDependencies": { "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" } }, "sha512-ntLrlgqkmZupbbjekz3fE/n3eQH2vhncx8gUp0+N+GttKWevx7jos11JUBjnJwb1RSOPgRUFcrluOqBp0VgcfQ=="], + "@react-aria/focus": ["@react-aria/focus@3.21.5", "", { "dependencies": { "@react-aria/interactions": "^3.27.1", "@react-aria/utils": "^3.33.1", "@react-types/shared": "^3.33.1", "@swc/helpers": "^0.5.0", "clsx": "^2.0.0" }, "peerDependencies": { "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" } }, "sha512-V18fwCyf8zqgJdpLQeDU5ZRNd9TeOfBbhLgmX77Zr5ae9XwaoJ1R3SFJG1wCJX60t34AW+aLZSEEK+saQElf3Q=="], - "@react-aria/ssr": ["@react-aria/ssr@3.9.8", "", { "dependencies": { "@swc/helpers": "^0.5.0" }, "peerDependencies": { "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" } }, "sha512-lQDE/c9uTfBSDOjaZUJS8xP2jCKVk4zjQeIlCH90xaLhHDgbpCdns3xvFpJJujfj3nI4Ll9K7A+ONUBDCASOuw=="], + "@react-aria/interactions": ["@react-aria/interactions@3.27.1", "", { "dependencies": { "@react-aria/ssr": "^3.9.10", "@react-aria/utils": "^3.33.1", "@react-stately/flags": "^3.1.2", "@react-types/shared": "^3.33.1", "@swc/helpers": "^0.5.0" }, "peerDependencies": { "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" } }, "sha512-M3wLpTTmDflI0QGNK0PJNUaBXXfeBXue8ZxLMngfc1piHNiH4G5lUvWd9W14XVbqrSCVY8i8DfGrNYpyyZu0tw=="], - "@react-aria/utils": ["@react-aria/utils@3.29.0", "", { "dependencies": { "@react-aria/ssr": "^3.9.8", "@react-stately/flags": "^3.1.1", "@react-stately/utils": "^3.10.6", "@react-types/shared": "^3.29.1", "@swc/helpers": "^0.5.0", "clsx": "^2.0.0" }, "peerDependencies": { "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" } }, "sha512-jSOrZimCuT1iKNVlhjIxDkAhgF7HSp3pqyT6qjg/ZoA0wfqCi/okmrMPiWSAKBnkgX93N8GYTLT3CIEO6WZe9Q=="], + "@react-aria/ssr": ["@react-aria/ssr@3.9.10", "", { "dependencies": { "@swc/helpers": "^0.5.0" }, "peerDependencies": { "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" } }, "sha512-hvTm77Pf+pMBhuBm760Li0BVIO38jv1IBws1xFm1NoL26PU+fe+FMW5+VZWyANR6nYL65joaJKZqOdTQMkO9IQ=="], - "@react-stately/flags": ["@react-stately/flags@3.1.1", "", { "dependencies": { "@swc/helpers": "^0.5.0" } }, "sha512-XPR5gi5LfrPdhxZzdIlJDz/B5cBf63l4q6/AzNqVWFKgd0QqY5LvWJftXkklaIUpKSJkIKQb8dphuZXDtkWNqg=="], + "@react-aria/utils": ["@react-aria/utils@3.33.1", "", { "dependencies": { "@react-aria/ssr": "^3.9.10", "@react-stately/flags": "^3.1.2", "@react-stately/utils": "^3.11.0", "@react-types/shared": "^3.33.1", "@swc/helpers": "^0.5.0", "clsx": "^2.0.0" }, "peerDependencies": { "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" } }, "sha512-kIx1Sj6bbAT0pdqCegHuPanR9zrLn5zMRiM7LN12rgRf55S19ptd9g3ncahArifYTRkfEU9VIn+q0HjfMqS9/w=="], - "@react-stately/utils": ["@react-stately/utils@3.10.6", "", { "dependencies": { "@swc/helpers": "^0.5.0" }, "peerDependencies": { "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" } }, "sha512-O76ip4InfTTzAJrg8OaZxKU4vvjMDOpfA/PGNOytiXwBbkct2ZeZwaimJ8Bt9W1bj5VsZ81/o/tW4BacbdDOMA=="], + "@react-stately/flags": ["@react-stately/flags@3.1.2", "", { "dependencies": { "@swc/helpers": "^0.5.0" } }, "sha512-2HjFcZx1MyQXoPqcBGALwWWmgFVUk2TuKVIQxCbRq7fPyWXIl6VHcakCLurdtYC2Iks7zizvz0Idv48MQ38DWg=="], - "@react-types/shared": ["@react-types/shared@3.29.1", "", { "peerDependencies": { "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" } }, "sha512-KtM+cDf2CXoUX439rfEhbnEdAgFZX20UP2A35ypNIawR7/PFFPjQDWyA2EnClCcW/dLWJDEPX2U8+EJff8xqmQ=="], + "@react-stately/utils": ["@react-stately/utils@3.11.0", "", { "dependencies": { "@swc/helpers": "^0.5.0" }, "peerDependencies": { "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" } }, "sha512-8LZpYowJ9eZmmYLpudbo/eclIRnbhWIJZ994ncmlKlouNzKohtM8qTC6B1w1pwUbiwGdUoyzLuQbeaIor5Dvcw=="], + + "@react-types/shared": ["@react-types/shared@3.33.1", "", { "peerDependencies": { "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" } }, "sha512-oJHtjvLG43VjwemQDadlR5g/8VepK56B/xKO2XORPHt9zlW6IZs3tZrYlvH29BMvoqC7RtE7E5UjgbnbFtDGag=="], "@rolldown/binding-android-arm64": ["@rolldown/binding-android-arm64@1.0.0-rc.12", "", { "os": "android", "cpu": "arm64" }, "sha512-pv1y2Fv0JybcykuiiD3qBOBdz6RteYojRFY1d+b95WVuzx211CRh+ytI/+9iVyWQ6koTh5dawe4S/yRfOFjgaA=="], @@ -229,9 +276,13 @@ "@rtsao/scc": ["@rtsao/scc@1.1.0", "", {}, "sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g=="], - "@sinclair/typebox": ["@sinclair/typebox@0.27.8", "", {}, "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA=="], + "@sinclair/typebox": ["@sinclair/typebox@0.27.10", "", {}, "sha512-MTBk/3jGLNB2tVxv6uLlFh1iu64iYOQ2PbdOSK3NW8JZsmlaOh2q6sdtKowBhfw8QFLmYNzTW4/oK4uATIi6ZA=="], + + "@sindresorhus/is": ["@sindresorhus/is@4.6.0", "", {}, "sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw=="], + + "@swc/helpers": ["@swc/helpers@0.5.21", "", { "dependencies": { "tslib": "^2.8.0" } }, "sha512-jI/VAmtdjB/RnI8GTnokyX7Ug8c+g+ffD6QRLa6XQewtnGyukKkKSk3wLTM3b5cjt1jNh9x0jfVlagdN2gDKQg=="], - "@swc/helpers": ["@swc/helpers@0.5.15", "", { "dependencies": { "tslib": "^2.8.0" } }, "sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g=="], + "@szmarczak/http-timer": ["@szmarczak/http-timer@4.0.6", "", { "dependencies": { "defer-to-connect": "^2.0.0" } }, "sha512-4BAffykYOgO+5nzBWYwE3W90sBgLJoUPRWWcL8wlyiM8IB8ipJz3UMJ9KXQd1RKQXpKp8Tutn80HZtWsu2u76w=="], "@tailwindcss/node": ["@tailwindcss/node@4.2.2", "", { "dependencies": { "@jridgewell/remapping": "^2.3.5", "enhanced-resolve": "^5.19.0", "jiti": "^2.6.1", "lightningcss": "1.32.0", "magic-string": "^0.30.21", "source-map-js": "^1.2.1", "tailwindcss": "4.2.2" } }, "sha512-pXS+wJ2gZpVXqFaUEjojq7jzMpTGf8rU6ipJz5ovJV6PUGmlJ+jvIwGrzdHdQ80Sg+wmQxUFuoW1UAAwHNEdFA=="], @@ -263,9 +314,9 @@ "@tailwindcss/postcss": ["@tailwindcss/postcss@4.2.2", "", { "dependencies": { "@alloc/quick-lru": "^5.2.0", "@tailwindcss/node": "4.2.2", "@tailwindcss/oxide": "4.2.2", "postcss": "^8.5.6", "tailwindcss": "4.2.2" } }, "sha512-n4goKQbW8RVXIbNKRB/45LzyUqN451deQK0nzIeauVEqjlI49slUlgKYJM2QyUzap/PcpnS7kzSUmPb1sCRvYQ=="], - "@tanstack/react-virtual": ["@tanstack/react-virtual@3.13.9", "", { "dependencies": { "@tanstack/virtual-core": "3.13.9" }, "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-SPWC8kwG/dWBf7Py7cfheAPOxuvIv4fFQ54PdmYbg7CpXfsKxkucak43Q0qKsxVthhUJQ1A7CIMAIplq4BjVwA=="], + "@tanstack/react-virtual": ["@tanstack/react-virtual@3.13.23", "", { "dependencies": { "@tanstack/virtual-core": "3.13.23" }, "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-XnMRnHQ23piOVj2bzJqHrRrLg4r+F86fuBcwteKfbIjJrtGxb4z7tIvPVAe4B+4UVwo9G4Giuz5fmapcrnZ0OQ=="], - "@tanstack/virtual-core": ["@tanstack/virtual-core@3.13.9", "", {}, "sha512-3jztt0jpaoJO5TARe2WIHC1UQC3VMLAFUW5mmMo0yrkwtDB2AQP0+sh10BVUpWrnvHjSLvzFizydtEGLCJKFoQ=="], + "@tanstack/virtual-core": ["@tanstack/virtual-core@3.13.23", "", {}, "sha512-zSz2Z2HNyLjCplANTDyl3BcdQJc2k1+yyFoKhNRmCr7V7dY8o8q5m8uFTI1/Pg1kL+Hgrz6u3Xo6eFUB7l66cg=="], "@tauri-apps/api": ["@tauri-apps/api@2.10.1", "", {}, "sha512-hKL/jWf293UDSUN09rR69hrToyIXBb8CjGaWC7gfinvnQrBVvnLr08FeFi38gxtugAVyVcTa5/FD/Xnkb1siBw=="], @@ -299,13 +350,13 @@ "@tauri-apps/plugin-clipboard-manager": ["@tauri-apps/plugin-clipboard-manager@2.3.2", "", { "dependencies": { "@tauri-apps/api": "^2.8.0" } }, "sha512-CUlb5Hqi2oZbcZf4VUyUH53XWPPdtpw43EUpCza5HWZJwxEoDowFzNUDt1tRUXA8Uq+XPn17Ysfptip33sG4eQ=="], - "@tauri-apps/plugin-deep-link": ["@tauri-apps/plugin-deep-link@2.4.7", "", { "dependencies": { "@tauri-apps/api": "^2.10.1" } }, "sha512-K0FQlLM6BoV7Ws2xfkh+Tnwi5VZVdkI4Vw/3AGLSf0Xvu2y86AMBzd9w/SpzKhw9ai2B6ES8di/OoGDCExkOzg=="], + "@tauri-apps/plugin-deep-link": ["@tauri-apps/plugin-deep-link@2.4.8", "", { "dependencies": { "@tauri-apps/api": "^2.10.1" } }, "sha512-Cd2Cs960MGuGONeIwxOPx9wqwedetAHOGlwK5boJ/SMTfAtAyfErpfVPEn+EJzgXsJun8EKzsEumHjr+64V4fw=="], - "@tauri-apps/plugin-dialog": ["@tauri-apps/plugin-dialog@2.6.0", "", { "dependencies": { "@tauri-apps/api": "^2.8.0" } }, "sha512-q4Uq3eY87TdcYzXACiYSPhmpBA76shgmQswGkSVio4C82Sz2W4iehe9TnKYwbq7weHiL88Yw19XZm7v28+Micg=="], + "@tauri-apps/plugin-dialog": ["@tauri-apps/plugin-dialog@2.7.0", "", { "dependencies": { "@tauri-apps/api": "^2.10.1" } }, "sha512-4nS/hfGMGCXiAS3LtVjH9AgsSAPJeG/7R+q8agTFqytjnMa4Zq95Bq8WzVDkckpanX+yyRHXnRtrKXkANKDHvw=="], - "@tauri-apps/plugin-fs": ["@tauri-apps/plugin-fs@2.4.5", "", { "dependencies": { "@tauri-apps/api": "^2.8.0" } }, "sha512-dVxWWGE6VrOxC7/jlhyE+ON/Cc2REJlM35R3PJX3UvFw2XwYhLGQVAIyrehenDdKjotipjYEVc4YjOl3qq90fA=="], + "@tauri-apps/plugin-fs": ["@tauri-apps/plugin-fs@2.5.0", "", { "dependencies": { "@tauri-apps/api": "^2.10.1" } }, "sha512-c83kbz61AK+rKjhS+je9+stIO27nXj7p9cqeg36TwkIUtxpCFTttlHHtqon6h6FN54cXjyAjlMPOJcW3mwE5XQ=="], - "@tauri-apps/plugin-http": ["@tauri-apps/plugin-http@2.5.7", "", { "dependencies": { "@tauri-apps/api": "^2.10.1" } }, "sha512-+F2lEH/c9b0zSsOXKq+5hZNcd9F4IIKCK1T17RqMwpCmVnx2aoqY8yIBccCd25HTYUb3j6NPVbRax/m00hKG8A=="], + "@tauri-apps/plugin-http": ["@tauri-apps/plugin-http@2.5.8", "", { "dependencies": { "@tauri-apps/api": "^2.10.1" } }, "sha512-oxd7oypzQeu8kAfFCrw534Kq7Cw+NzozcnCY21O4rz3A+veJiIiuSCMIprgGcZOcLAXFP9GmDhKUbhuKWcunRw=="], "@tauri-apps/plugin-notification": ["@tauri-apps/plugin-notification@2.3.3", "", { "dependencies": { "@tauri-apps/api": "^2.8.0" } }, "sha512-Zw+ZH18RJb41G4NrfHgIuofJiymusqN+q8fGUIIV7vyCH+5sSn5coqRv/MWB9qETsUs97vmU045q7OyseCV3Qg=="], @@ -315,39 +366,61 @@ "@tauri-apps/plugin-store": ["@tauri-apps/plugin-store@2.4.2", "", { "dependencies": { "@tauri-apps/api": "^2.8.0" } }, "sha512-0ClHS50Oq9HEvLPhNzTNFxbWVOqoAp3dRvtewQBeqfIQ0z5m3JRnOISIn2ZVPCrQC0MyGyhTS9DWhHjpigQE7A=="], - "@tauri-apps/plugin-updater": ["@tauri-apps/plugin-updater@2.10.0", "", { "dependencies": { "@tauri-apps/api": "^2.10.1" } }, "sha512-ljN8jPlnT0aSn8ecYhuBib84alxfMx6Hc8vJSKMJyzGbTPFZAC44T2I1QNFZssgWKrAlofvJqCC6Rr472JWfkQ=="], + "@tauri-apps/plugin-updater": ["@tauri-apps/plugin-updater@2.10.1", "", { "dependencies": { "@tauri-apps/api": "^2.10.1" } }, "sha512-NFYMg+tWOZPJdzE/PpFj2qfqwAWwNS3kXrb1tm1gnBJ9mYzZ4WDRrwy8udzWoAnfGCHLuePNLY1WVCNHnh3eRA=="], "@tybys/wasm-util": ["@tybys/wasm-util@0.10.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg=="], + "@types/cacheable-request": ["@types/cacheable-request@6.0.3", "", { "dependencies": { "@types/http-cache-semantics": "*", "@types/keyv": "^3.1.4", "@types/node": "*", "@types/responselike": "^1.0.0" } }, "sha512-IQ3EbTzGxIigb1I3qPZc1rWJnH0BmSKv5QYTalEwweFvyBDLSAe24zP0le/hyi7ecGfZVlIVAg4BZqb8WBwKqw=="], + + "@types/debug": ["@types/debug@4.1.13", "", { "dependencies": { "@types/ms": "*" } }, "sha512-KSVgmQmzMwPlmtljOomayoR89W4FynCAi3E8PPs7vmDVPe84hT+vGPKkJfThkmXs0x0jAaa9U8uW8bbfyS2fWw=="], + "@types/esrecurse": ["@types/esrecurse@4.3.1", "", {}, "sha512-xJBAbDifo5hpffDBuHl0Y8ywswbiAp/Wi7Y/GtAgSlZyIABppyurxVueOPE8LUQOxdlgi6Zqce7uoEpqNTeiUw=="], "@types/estree": ["@types/estree@1.0.8", "", {}, "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w=="], + "@types/fs-extra": ["@types/fs-extra@9.0.13", "", { "dependencies": { "@types/node": "*" } }, "sha512-nEnwB++1u5lVDM2UI4c1+5R+FYaKfaAzS4OococimjVm3nQw3TuzH5UNsocrcTBbhnerblyHj4A49qXbIiZdpA=="], + + "@types/http-cache-semantics": ["@types/http-cache-semantics@4.2.0", "", {}, "sha512-L3LgimLHXtGkWikKnsPg0/VFx9OGZaC+eN1u4r+OB1XRqH3meBIAVC2zr1WdMH+RHmnRkqliQAOHNJ/E0j/e0Q=="], + "@types/json-schema": ["@types/json-schema@7.0.15", "", {}, "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA=="], "@types/json5": ["@types/json5@0.0.29", "", {}, "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ=="], + "@types/keyv": ["@types/keyv@3.1.4", "", { "dependencies": { "@types/node": "*" } }, "sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg=="], + + "@types/ms": ["@types/ms@2.1.0", "", {}, "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA=="], + + "@types/node": ["@types/node@25.5.2", "", { "dependencies": { "undici-types": "~7.18.0" } }, "sha512-tO4ZIRKNC+MDWV4qKVZe3Ql/woTnmHDr5JD8UI5hn2pwBrHEwOEMZK7WlNb5RKB6EoJ02gwmQS9OrjuFnZYdpg=="], + + "@types/plist": ["@types/plist@3.0.5", "", { "dependencies": { "@types/node": "*", "xmlbuilder": ">=11.0.1" } }, "sha512-E6OCaRmAe4WDmWNsL/9RMqdkkzDCY1etutkflWk4c+AcjDU07Pcz1fQwTX0TQz+Pxqn9i4L1TU3UFpjnrcDgxA=="], + "@types/react": ["@types/react@19.2.14", "", { "dependencies": { "csstype": "^3.2.2" } }, "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w=="], "@types/react-dom": ["@types/react-dom@19.2.3", "", { "peerDependencies": { "@types/react": "^19.2.0" } }, "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ=="], - "@typescript-eslint/eslint-plugin": ["@typescript-eslint/eslint-plugin@8.57.2", "", { "dependencies": { "@eslint-community/regexpp": "^4.12.2", "@typescript-eslint/scope-manager": "8.57.2", "@typescript-eslint/type-utils": "8.57.2", "@typescript-eslint/utils": "8.57.2", "@typescript-eslint/visitor-keys": "8.57.2", "ignore": "^7.0.5", "natural-compare": "^1.4.0", "ts-api-utils": "^2.4.0" }, "peerDependencies": { "@typescript-eslint/parser": "^8.57.2", "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-NZZgp0Fm2IkD+La5PR81sd+g+8oS6JwJje+aRWsDocxHkjyRw0J5L5ZTlN3LI1LlOcGL7ph3eaIUmTXMIjLk0w=="], + "@types/responselike": ["@types/responselike@1.0.3", "", { "dependencies": { "@types/node": "*" } }, "sha512-H/+L+UkTV33uf49PH5pCAUBVPNj2nDBXTN+qS1dOwyyg24l3CcicicCA7ca+HMvJBZcFgl5r8e+RR6elsb4Lyw=="], + + "@types/verror": ["@types/verror@1.10.11", "", {}, "sha512-RlDm9K7+o5stv0Co8i8ZRGxDbrTxhJtgjqjFyVh/tXQyl/rYtTKlnTvZ88oSTeYREWurwx20Js4kTuKCsFkUtg=="], + + "@types/yauzl": ["@types/yauzl@2.10.3", "", { "dependencies": { "@types/node": "*" } }, "sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q=="], + + "@typescript-eslint/eslint-plugin": ["@typescript-eslint/eslint-plugin@8.58.0", "", { "dependencies": { "@eslint-community/regexpp": "^4.12.2", "@typescript-eslint/scope-manager": "8.58.0", "@typescript-eslint/type-utils": "8.58.0", "@typescript-eslint/utils": "8.58.0", "@typescript-eslint/visitor-keys": "8.58.0", "ignore": "^7.0.5", "natural-compare": "^1.4.0", "ts-api-utils": "^2.5.0" }, "peerDependencies": { "@typescript-eslint/parser": "^8.58.0", "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-RLkVSiNuUP1C2ROIWfqX+YcUfLaSnxGE/8M+Y57lopVwg9VTYYfhuz15Yf1IzCKgZj6/rIbYTmJCUSqr76r0Wg=="], "@typescript-eslint/parser": ["@typescript-eslint/parser@6.21.0", "", { "dependencies": { "@typescript-eslint/scope-manager": "6.21.0", "@typescript-eslint/types": "6.21.0", "@typescript-eslint/typescript-estree": "6.21.0", "@typescript-eslint/visitor-keys": "6.21.0", "debug": "^4.3.4" }, "peerDependencies": { "eslint": "^7.0.0 || ^8.0.0" } }, "sha512-tbsV1jPne5CkFQCgPBcDOt30ItF7aJoZL997JSF7MhGQqOeT3svWRYxiqlfA5RUdlHN6Fi+EI9bxqbdyAUZjYQ=="], - "@typescript-eslint/project-service": ["@typescript-eslint/project-service@8.57.2", "", { "dependencies": { "@typescript-eslint/tsconfig-utils": "^8.57.2", "@typescript-eslint/types": "^8.57.2", "debug": "^4.4.3" }, "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-FuH0wipFywXRTHf+bTTjNyuNQQsQC3qh/dYzaM4I4W0jrCqjCVuUh99+xd9KamUfmCGPvbO8NDngo/vsnNVqgw=="], + "@typescript-eslint/project-service": ["@typescript-eslint/project-service@8.58.0", "", { "dependencies": { "@typescript-eslint/tsconfig-utils": "^8.58.0", "@typescript-eslint/types": "^8.58.0", "debug": "^4.4.3" }, "peerDependencies": { "typescript": ">=4.8.4 <6.1.0" } }, "sha512-8Q/wBPWLQP1j16NxoPNIKpDZFMaxl7yWIoqXWYeWO+Bbd2mjgvoF0dxP2jKZg5+x49rgKdf7Ck473M8PC3V9lg=="], "@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@6.21.0", "", { "dependencies": { "@typescript-eslint/types": "6.21.0", "@typescript-eslint/visitor-keys": "6.21.0" } }, "sha512-OwLUIWZJry80O99zvqXVEioyniJMa+d2GrqpUTqi5/v5D5rOrppJVBPa0yKCblcigC0/aYAzxxqQ1B+DS2RYsg=="], - "@typescript-eslint/tsconfig-utils": ["@typescript-eslint/tsconfig-utils@8.57.2", "", { "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-3Lm5DSM+DCowsUOJC+YqHHnKEfFh5CoGkj5Z31NQSNF4l5wdOwqGn99wmwN/LImhfY3KJnmordBq/4+VDe2eKw=="], + "@typescript-eslint/tsconfig-utils": ["@typescript-eslint/tsconfig-utils@8.58.0", "", { "peerDependencies": { "typescript": ">=4.8.4 <6.1.0" } }, "sha512-doNSZEVJsWEu4htiVC+PR6NpM+pa+a4ClH9INRWOWCUzMst/VA9c4gXq92F8GUD1rwhNvRLkgjfYtFXegXQF7A=="], - "@typescript-eslint/type-utils": ["@typescript-eslint/type-utils@8.57.2", "", { "dependencies": { "@typescript-eslint/types": "8.57.2", "@typescript-eslint/typescript-estree": "8.57.2", "@typescript-eslint/utils": "8.57.2", "debug": "^4.4.3", "ts-api-utils": "^2.4.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-Co6ZCShm6kIbAM/s+oYVpKFfW7LBc6FXoPXjTRQ449PPNBY8U0KZXuevz5IFuuUj2H9ss40atTaf9dlGLzbWZg=="], + "@typescript-eslint/type-utils": ["@typescript-eslint/type-utils@8.58.0", "", { "dependencies": { "@typescript-eslint/types": "8.58.0", "@typescript-eslint/typescript-estree": "8.58.0", "@typescript-eslint/utils": "8.58.0", "debug": "^4.4.3", "ts-api-utils": "^2.5.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-aGsCQImkDIqMyx1u4PrVlbi/krmDsQUs4zAcCV6M7yPcPev+RqVlndsJy9kJ8TLihW9TZ0kbDAzctpLn5o+lOg=="], "@typescript-eslint/types": ["@typescript-eslint/types@6.21.0", "", {}, "sha512-1kFmZ1rOm5epu9NZEZm1kckCDGj5UJEf7P1kliH4LKu/RkwpsfqqGmY2OOcUs18lSlQBKLDYBOGxRVtrMN5lpg=="], - "@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@8.57.2", "", { "dependencies": { "@typescript-eslint/project-service": "8.57.2", "@typescript-eslint/tsconfig-utils": "8.57.2", "@typescript-eslint/types": "8.57.2", "@typescript-eslint/visitor-keys": "8.57.2", "debug": "^4.4.3", "minimatch": "^10.2.2", "semver": "^7.7.3", "tinyglobby": "^0.2.15", "ts-api-utils": "^2.4.0" }, "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-2MKM+I6g8tJxfSmFKOnHv2t8Sk3T6rF20A1Puk0svLK+uVapDZB/4pfAeB7nE83uAZrU6OxW+HmOd5wHVdXwXA=="], + "@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@8.58.0", "", { "dependencies": { "@typescript-eslint/project-service": "8.58.0", "@typescript-eslint/tsconfig-utils": "8.58.0", "@typescript-eslint/types": "8.58.0", "@typescript-eslint/visitor-keys": "8.58.0", "debug": "^4.4.3", "minimatch": "^10.2.2", "semver": "^7.7.3", "tinyglobby": "^0.2.15", "ts-api-utils": "^2.5.0" }, "peerDependencies": { "typescript": ">=4.8.4 <6.1.0" } }, "sha512-7vv5UWbHqew/dvs+D3e1RvLv1v2eeZ9txRHPnEEBUgSNLx5ghdzjHa0sgLWYVKssH+lYmV0JaWdoubo0ncGYLA=="], - "@typescript-eslint/utils": ["@typescript-eslint/utils@8.57.2", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.9.1", "@typescript-eslint/scope-manager": "8.57.2", "@typescript-eslint/types": "8.57.2", "@typescript-eslint/typescript-estree": "8.57.2" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-krRIbvPK1ju1WBKIefiX+bngPs+odIQUtR7kymzPfo1POVw3jlF+nLkmexdSSd4UCbDcQn+wMBATOOmpBbqgKg=="], + "@typescript-eslint/utils": ["@typescript-eslint/utils@8.58.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.9.1", "@typescript-eslint/scope-manager": "8.58.0", "@typescript-eslint/types": "8.58.0", "@typescript-eslint/typescript-estree": "8.58.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-RfeSqcFeHMHlAWzt4TBjWOAtoW9lnsAGiP3GbaX9uVgTYYrMbVnGONEfUCiSss+xMHFl+eHZiipmA8WkQ7FuNA=="], "@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@6.21.0", "", { "dependencies": { "@typescript-eslint/types": "6.21.0", "eslint-visitor-keys": "^3.4.1" } }, "sha512-JJtkDduxLi9bivAB+cYOVMtbkqdPOhZ+ZI5LC47MIRrDV4Yn2o+ZnW10Nkmr28xRpSpdJ6Sm42Hjf2+REYXm0A=="], @@ -355,6 +428,10 @@ "@vitejs/plugin-react": ["@vitejs/plugin-react@6.0.1", "", { "dependencies": { "@rolldown/pluginutils": "1.0.0-rc.7" }, "peerDependencies": { "@rolldown/plugin-babel": "^0.1.7 || ^0.2.0", "babel-plugin-react-compiler": "^1.0.0", "vite": "^8.0.0" }, "optionalPeers": ["@rolldown/plugin-babel", "babel-plugin-react-compiler"] }, "sha512-l9X/E3cDb+xY3SWzlG1MOGt2usfEHGMNIaegaUGFsLkb3RCn/k8/TOXBcab+OndDI4TBtktT8/9BwwW8Vi9KUQ=="], + "@xmldom/xmldom": ["@xmldom/xmldom@0.8.12", "", {}, "sha512-9k/gHF6n/pAi/9tqr3m3aqkuiNosYTurLLUtc7xQ9sxB/wm7WPygCv8GYa6mS0fLJEHhqMC1ATYhz++U/lRHqg=="], + + "abbrev": ["abbrev@3.0.1", "", {}, "sha512-AO2ac6pjRB3SJmGJo+v5/aK6Omggp6fsLrs6wN9bd35ulu4cCwaAU9+7ZhXjeqHVkaHThLuzH0nZr0YpCDhygg=="], + "acorn": ["acorn@8.16.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw=="], "acorn-jsx": ["acorn-jsx@5.3.2", "", { "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ=="], @@ -363,9 +440,15 @@ "ajv": ["ajv@6.14.0", "", { "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", "json-schema-traverse": "^0.4.1", "uri-js": "^4.2.2" } }, "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw=="], + "ajv-keywords": ["ajv-keywords@3.5.2", "", { "peerDependencies": { "ajv": "^6.9.1" } }, "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ=="], + "ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], - "ansi-styles": ["ansi-styles@5.2.0", "", {}, "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA=="], + "ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="], + + "app-builder-bin": ["app-builder-bin@5.0.0-alpha.12", "", {}, "sha512-j87o0j6LqPL3QRr8yid6c+Tt5gC7xNfYo6uQIQkorAC6MpeayVMZrEDzKmJJ/Hlv7EnOQpaRm53k6ktDYZyB6w=="], + + "app-builder-lib": ["app-builder-lib@26.8.1", "", { "dependencies": { "@develar/schema-utils": "~2.6.5", "@electron/asar": "3.4.1", "@electron/fuses": "^1.8.0", "@electron/get": "^3.0.0", "@electron/notarize": "2.5.0", "@electron/osx-sign": "1.3.3", "@electron/rebuild": "^4.0.3", "@electron/universal": "2.0.3", "@malept/flatpak-bundler": "^0.4.0", "@types/fs-extra": "9.0.13", "async-exit-hook": "^2.0.1", "builder-util": "26.8.1", "builder-util-runtime": "9.5.1", "chromium-pickle-js": "^0.2.0", "ci-info": "4.3.1", "debug": "^4.3.4", "dotenv": "^16.4.5", "dotenv-expand": "^11.0.6", "ejs": "^3.1.8", "electron-publish": "26.8.1", "fs-extra": "^10.1.0", "hosted-git-info": "^4.1.0", "isbinaryfile": "^5.0.0", "jiti": "^2.4.2", "js-yaml": "^4.1.0", "json5": "^2.2.3", "lazy-val": "^1.0.5", "minimatch": "^10.0.3", "plist": "3.1.0", "proper-lockfile": "^4.1.2", "resedit": "^1.7.0", "semver": "~7.7.3", "tar": "^7.5.7", "temp-file": "^3.4.0", "tiny-async-pool": "1.3.0", "which": "^5.0.0" }, "peerDependencies": { "dmg-builder": "26.8.1", "electron-builder-squirrel-windows": "26.8.1" } }, "sha512-p0Im/Dx5C4tmz8QEE1Yn4MkuPC8PrnlRneMhWJj7BBXQfNTJUshM/bp3lusdEsDbvvfJZpXWnYesgSLvwtM2Zw=="], "argparse": ["argparse@2.0.1", "", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="], @@ -389,15 +472,27 @@ "arraybuffer.prototype.slice": ["arraybuffer.prototype.slice@1.0.4", "", { "dependencies": { "array-buffer-byte-length": "^1.0.1", "call-bind": "^1.0.8", "define-properties": "^1.2.1", "es-abstract": "^1.23.5", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.6", "is-array-buffer": "^3.0.4" } }, "sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ=="], + "assert-plus": ["assert-plus@1.0.0", "", {}, "sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw=="], + "ast-types-flow": ["ast-types-flow@0.0.8", "", {}, "sha512-OH/2E5Fg20h2aPrbe+QL8JZQFko0YZaF+j4mnQ7BGhfavO7OpSLa8a0y9sBwomHdSbkhTS8TQNayBfnW5DwbvQ=="], + "astral-regex": ["astral-regex@2.0.0", "", {}, "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ=="], + + "async": ["async@3.2.6", "", {}, "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA=="], + + "async-exit-hook": ["async-exit-hook@2.0.1", "", {}, "sha512-NW2cX8m1Q7KPA7a5M2ULQeZ2wR5qI5PAbw5L0UOMxdioVk9PMZ0h1TmyZEkPYrCvYjDlFICusOu1dlEKAAeXBw=="], + "async-function": ["async-function@1.0.0", "", {}, "sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA=="], + "asynckit": ["asynckit@0.4.0", "", {}, "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="], + + "at-least-node": ["at-least-node@1.0.0", "", {}, "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg=="], + "autoprefixer": ["autoprefixer@10.4.27", "", { "dependencies": { "browserslist": "^4.28.1", "caniuse-lite": "^1.0.30001774", "fraction.js": "^5.3.4", "picocolors": "^1.1.1", "postcss-value-parser": "^4.2.0" }, "peerDependencies": { "postcss": "^8.1.0" }, "bin": { "autoprefixer": "bin/autoprefixer" } }, "sha512-NP9APE+tO+LuJGn7/9+cohklunJsXWiaWEfV3si4Gi/XHDwVNgkwr1J3RQYFIvPy76GmJ9/bW8vyoU1LcxwKHA=="], "available-typed-arrays": ["available-typed-arrays@1.0.7", "", { "dependencies": { "possible-typed-array-names": "^1.0.0" } }, "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ=="], - "axe-core": ["axe-core@4.11.1", "", {}, "sha512-BASOg+YwO2C+346x3LZOeoovTIoTrRqEsqMa6fmfAV0P+U9mFr9NsyOEpiYvFjbc64NMrSswhV50WdXzdb/Z5A=="], + "axe-core": ["axe-core@4.11.2", "", {}, "sha512-byD6KPdvo72y/wj2T/4zGEvvlis+PsZsn/yPS3pEO+sFpcrqRpX/TJCxvVaEsNeMrfQbCr7w163YqoD9IYwHXw=="], "axobject-query": ["axobject-query@4.1.0", "", {}, "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ=="], @@ -405,17 +500,39 @@ "balanced-match": ["balanced-match@4.0.4", "", {}, "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA=="], - "baseline-browser-mapping": ["baseline-browser-mapping@2.9.7", "", { "bin": { "baseline-browser-mapping": "dist/cli.js" } }, "sha512-k9xFKplee6KIio3IDbwj+uaCLpqzOwakOgmqzPezM0sFJlFKcg30vk2wOiAJtkTSfx0SSQDSe8q+mWA/fSH5Zg=="], + "base64-js": ["base64-js@1.5.1", "", {}, "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA=="], + + "baseline-browser-mapping": ["baseline-browser-mapping@2.10.14", "", { "bin": { "baseline-browser-mapping": "dist/cli.cjs" } }, "sha512-fOVLPAsFTsQfuCkvahZkzq6nf8KvGWanlYoTh0SVA0A/PIUxQGU2AOZAoD95n2gFLVDW/jP6sbGLny95nmEuHA=="], "binary-search": ["binary-search@1.3.6", "", {}, "sha512-nbE1WxOTTrUWIfsfZ4aHGYu5DOuNkbxGokjV6Z2kxfJK3uaAb8zNK1muzOeipoLHZjInT4Br88BHpzevc681xA=="], - "brace-expansion": ["brace-expansion@5.0.4", "", { "dependencies": { "balanced-match": "^4.0.2" } }, "sha512-h+DEnpVvxmfVefa4jFbCf5HdH5YMDXRsmKflpf1pILZWRFlTbJpxeU55nJl4Smt5HQaGzg1o6RHFPJaOqnmBDg=="], + "bl": ["bl@4.1.0", "", { "dependencies": { "buffer": "^5.5.0", "inherits": "^2.0.4", "readable-stream": "^3.4.0" } }, "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w=="], + + "boolean": ["boolean@3.2.0", "", {}, "sha512-d0II/GO9uf9lfUHH2BQsjxzRJZBdsjgsBiW4BvhWk/3qoKwQFjIDVN19PfX8F2D/r9PCMTtLWjYVCFrpeYUzsw=="], + + "brace-expansion": ["brace-expansion@5.0.5", "", { "dependencies": { "balanced-match": "^4.0.2" } }, "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ=="], "braces": ["braces@3.0.3", "", { "dependencies": { "fill-range": "^7.1.1" } }, "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA=="], "bresenham-zingl": ["bresenham-zingl@0.2.5", "", {}, "sha512-0TCrPvavRM/3Os9QIUDhe8jpAQXgEYjsVO+9IKnkC8cdzorDX5fMRfr3neEFGjcfRItEswHvIvPrqQzCalRYkw=="], - "browserslist": ["browserslist@4.28.1", "", { "dependencies": { "baseline-browser-mapping": "^2.9.0", "caniuse-lite": "^1.0.30001759", "electron-to-chromium": "^1.5.263", "node-releases": "^2.0.27", "update-browserslist-db": "^1.2.0" }, "bin": { "browserslist": "cli.js" } }, "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA=="], + "browserslist": ["browserslist@4.28.2", "", { "dependencies": { "baseline-browser-mapping": "^2.10.12", "caniuse-lite": "^1.0.30001782", "electron-to-chromium": "^1.5.328", "node-releases": "^2.0.36", "update-browserslist-db": "^1.2.3" }, "bin": { "browserslist": "cli.js" } }, "sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg=="], + + "buffer": ["buffer@5.7.1", "", { "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.1.13" } }, "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ=="], + + "buffer-crc32": ["buffer-crc32@0.2.13", "", {}, "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ=="], + + "buffer-from": ["buffer-from@1.1.2", "", {}, "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ=="], + + "builder-util": ["builder-util@26.8.1", "", { "dependencies": { "7zip-bin": "~5.2.0", "@types/debug": "^4.1.6", "app-builder-bin": "5.0.0-alpha.12", "builder-util-runtime": "9.5.1", "chalk": "^4.1.2", "cross-spawn": "^7.0.6", "debug": "^4.3.4", "fs-extra": "^10.1.0", "http-proxy-agent": "^7.0.0", "https-proxy-agent": "^7.0.0", "js-yaml": "^4.1.0", "sanitize-filename": "^1.6.3", "source-map-support": "^0.5.19", "stat-mode": "^1.0.0", "temp-file": "^3.4.0", "tiny-async-pool": "1.3.0" } }, "sha512-pm1lTYbGyc90DHgCDO7eo8Rl4EqKLciayNbZqGziqnH9jrlKe8ZANGdityLZU+pJh16dfzjAx2xQq9McuIPEtw=="], + + "builder-util-runtime": ["builder-util-runtime@9.5.1", "", { "dependencies": { "debug": "^4.3.4", "sax": "^1.2.4" } }, "sha512-qt41tMfgHTllhResqM5DcnHyDIWNgzHvuY2jDcYP9iaGpkWxTUzV6GQjDeLnlR1/DtdlcsWQbA7sByMpmJFTLQ=="], + + "cacache": ["cacache@19.0.1", "", { "dependencies": { "@npmcli/fs": "^4.0.0", "fs-minipass": "^3.0.0", "glob": "^10.2.2", "lru-cache": "^10.0.1", "minipass": "^7.0.3", "minipass-collect": "^2.0.1", "minipass-flush": "^1.0.5", "minipass-pipeline": "^1.2.4", "p-map": "^7.0.2", "ssri": "^12.0.0", "tar": "^7.4.3", "unique-filename": "^4.0.0" } }, "sha512-hdsUxulXCi5STId78vRVYEtDAjq99ICAUktLTeTYsLoTE6Z8dS0c8pWNCxwdrk9YfJeobDZc2Y186hD/5ZQgFQ=="], + + "cacheable-lookup": ["cacheable-lookup@5.0.4", "", {}, "sha512-2/kNscPhpcxrOigMZzbiWF7dz8ilhb/nIHU3EyZiXWXpeq/au8qJ8VhdftMkty3n7Gj6HIGalQG8oiBNB3AJgA=="], + + "cacheable-request": ["cacheable-request@7.0.4", "", { "dependencies": { "clone-response": "^1.0.2", "get-stream": "^5.1.0", "http-cache-semantics": "^4.0.0", "keyv": "^4.0.0", "lowercase-keys": "^2.0.0", "normalize-url": "^6.0.1", "responselike": "^2.0.0" } }, "sha512-v+p6ongsrp0yTGbJXjgxPow2+DL93DASP4kXCDKb8/bwRtt9OEF3whggkkDkGNzgcWy2XaF4a8nZglC7uElscg=="], "call-bind": ["call-bind@1.0.8", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.0", "es-define-property": "^1.0.0", "get-intrinsic": "^1.2.4", "set-function-length": "^1.2.2" } }, "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww=="], @@ -425,11 +542,31 @@ "callsites": ["callsites@3.1.0", "", {}, "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ=="], - "caniuse-lite": ["caniuse-lite@1.0.30001776", "", {}, "sha512-sg01JDPzZ9jGshqKSckOQthXnYwOEP50jeVFhaSFbZcOy05TiuuaffDOfcwtCisJ9kNQuLBFibYywv2Bgm9osw=="], + "caniuse-lite": ["caniuse-lite@1.0.30001785", "", {}, "sha512-blhOL/WNR+Km1RI/LCVAvA73xplXA7ZbjzI4YkMK9pa6T/P3F2GxjNpEkyw5repTw9IvkyrjyHpwjnhZ5FOvYQ=="], "chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="], - "cheminfo-types": ["cheminfo-types@1.10.0", "", {}, "sha512-lDoOWfctAQPQrrhydtdb2vV3S8RDuRNp62lzs/gIjNqMNubhsvqr+hI8XQJSy8X1ZXpGvzIQNGBYy4SVrKQNaQ=="], + "cheminfo-types": ["cheminfo-types@1.15.0", "", {}, "sha512-shv45WN2u0yN9EHH1bisNrv+fy4Cw+eLM5lOoriP67mePrwbHZ1kJqg90C8GEU7K1A8gJsicEoVZHcuBbuul/w=="], + + "chokidar": ["chokidar@5.0.0", "", { "dependencies": { "readdirp": "^5.0.0" } }, "sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw=="], + + "chownr": ["chownr@3.0.0", "", {}, "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g=="], + + "chromium-pickle-js": ["chromium-pickle-js@0.2.0", "", {}, "sha512-1R5Fho+jBq0DDydt+/vHWj5KJNJCKdARKOCwZUen84I5BreWoLqRLANH1U87eJy1tiASPtMnGqJJq0ZsLoRPOw=="], + + "ci-info": ["ci-info@4.4.0", "", {}, "sha512-77PSwercCZU2Fc4sX94eF8k8Pxte6JAwL4/ICZLFjJLqegs7kCuAsqqj/70NQF6TvDpgFjkubQB2FW2ZZddvQg=="], + + "cli-cursor": ["cli-cursor@3.1.0", "", { "dependencies": { "restore-cursor": "^3.1.0" } }, "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw=="], + + "cli-spinners": ["cli-spinners@2.9.2", "", {}, "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg=="], + + "cli-truncate": ["cli-truncate@2.1.0", "", { "dependencies": { "slice-ansi": "^3.0.0", "string-width": "^4.2.0" } }, "sha512-n8fOixwDD6b/ObinzTrp1ZKFzbgvKZvuz/TvejnLn1aQfC6r52XEx85FmuC+3HI+JM7coBRXUvNqEU2PHVrHpg=="], + + "cliui": ["cliui@8.0.1", "", { "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.1", "wrap-ansi": "^7.0.0" } }, "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ=="], + + "clone": ["clone@1.0.4", "", {}, "sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg=="], + + "clone-response": ["clone-response@1.0.3", "", { "dependencies": { "mimic-response": "^1.0.0" } }, "sha512-ROoL94jJH2dUVML2Y/5PEDNaSHgeOdSDicUyS7izcF63G6sTc/FTjLub4b8Il9S8S0beOfYt0TaA5qvFK+w0wA=="], "clsx": ["clsx@2.1.1", "", {}, "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA=="], @@ -439,14 +576,24 @@ "colord": ["colord@2.9.3", "", {}, "sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw=="], + "combined-stream": ["combined-stream@1.0.8", "", { "dependencies": { "delayed-stream": "~1.0.0" } }, "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg=="], + "commander": ["commander@14.0.3", "", {}, "sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw=="], "common-tags": ["common-tags@1.8.2", "", {}, "sha512-gk/Z852D2Wtb//0I+kRFNKKE9dIIVirjoqPoA1wJU+XePVXZfGeBpk45+A1rKO4Q43prqWBNY/MiIeRLbPWUaA=="], + "compare-version": ["compare-version@0.1.2", "", {}, "sha512-pJDh5/4wrEnXX/VWRZvruAGHkzKdr46z11OlTPN+VrATlWWhSKewNCJ1futCO5C7eJB3nPMFZA1LeYtcFboZ2A=="], + "concat-map": ["concat-map@0.0.1", "", {}, "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="], "convert-source-map": ["convert-source-map@2.0.0", "", {}, "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg=="], + "core-util-is": ["core-util-is@1.0.2", "", {}, "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ=="], + + "crc": ["crc@3.8.0", "", { "dependencies": { "buffer": "^5.1.0" } }, "sha512-iX3mfgcTMIq3ZKLIsVFAbv7+Mc10kxabAGQb8HvjA1o3T1PIYprbakQ65d3I+2HGHt6nSKkM9PYjgoJO2KcFBQ=="], + + "cross-dirname": ["cross-dirname@0.1.0", "", {}, "sha512-+R08/oI0nl3vfPcqftZRpytksBXDzOUveBq/NBVx0sUp1axwzPQrKinNx5yd5sxPu8j1wIy8AfnVQ+5eFdha6Q=="], + "cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="], "csstype": ["csstype@3.2.3", "", {}, "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ=="], @@ -461,27 +608,71 @@ "debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="], + "decompress-response": ["decompress-response@6.0.0", "", { "dependencies": { "mimic-response": "^3.1.0" } }, "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ=="], + "deep-is": ["deep-is@0.1.4", "", {}, "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ=="], + "defaults": ["defaults@1.0.4", "", { "dependencies": { "clone": "^1.0.2" } }, "sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A=="], + + "defer-to-connect": ["defer-to-connect@2.0.1", "", {}, "sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg=="], + "define-data-property": ["define-data-property@1.1.4", "", { "dependencies": { "es-define-property": "^1.0.0", "es-errors": "^1.3.0", "gopd": "^1.0.1" } }, "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A=="], "define-properties": ["define-properties@1.2.1", "", { "dependencies": { "define-data-property": "^1.0.1", "has-property-descriptors": "^1.0.0", "object-keys": "^1.1.1" } }, "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg=="], - "detect-libc": ["detect-libc@2.0.4", "", {}, "sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA=="], + "delayed-stream": ["delayed-stream@1.0.0", "", {}, "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ=="], + + "detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="], + + "detect-node": ["detect-node@2.1.0", "", {}, "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g=="], + + "dir-compare": ["dir-compare@4.2.0", "", { "dependencies": { "minimatch": "^3.0.5", "p-limit": "^3.1.0 " } }, "sha512-2xMCmOoMrdQIPHdsTawECdNPwlVFB9zGcz3kuhmBO6U3oU+UQjsue0i8ayLKpgBcm+hcXPMVSGUN9d+pvJ6+VQ=="], "dir-glob": ["dir-glob@3.0.1", "", { "dependencies": { "path-type": "^4.0.0" } }, "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA=="], "dlv": ["dlv@1.1.3", "", {}, "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA=="], + "dmg-builder": ["dmg-builder@26.8.1", "", { "dependencies": { "app-builder-lib": "26.8.1", "builder-util": "26.8.1", "fs-extra": "^10.1.0", "iconv-lite": "^0.6.2", "js-yaml": "^4.1.0" }, "optionalDependencies": { "dmg-license": "^1.0.11" } }, "sha512-glMJgnTreo8CFINujtAhCgN96QAqApDMZ8Vl1r8f0QT8QprvC1UCltV4CcWj20YoIyLZx6IUskaJZ0NV8fokcg=="], + + "dmg-license": ["dmg-license@1.0.11", "", { "dependencies": { "@types/plist": "^3.0.1", "@types/verror": "^1.10.3", "ajv": "^6.10.0", "crc": "^3.8.0", "iconv-corefoundation": "^1.1.7", "plist": "^3.0.4", "smart-buffer": "^4.0.2", "verror": "^1.10.0" }, "os": "darwin", "bin": { "dmg-license": "bin/dmg-license.js" } }, "sha512-ZdzmqwKmECOWJpqefloC5OJy1+WZBBse5+MR88z9g9Zn4VY+WYUkAyojmhzJckH5YbbZGcYIuGAkY5/Ys5OM2Q=="], + "doctrine": ["doctrine@2.1.0", "", { "dependencies": { "esutils": "^2.0.2" } }, "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw=="], + "dotenv": ["dotenv@16.6.1", "", {}, "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow=="], + + "dotenv-expand": ["dotenv-expand@11.0.7", "", { "dependencies": { "dotenv": "^16.4.5" } }, "sha512-zIHwmZPRshsCdpMDyVsqGmgyP0yT8GAgXUnkdAoJisxvf33k7yO6OuoKmcTGuXPWSsm8Oh88nZicRLA9Y0rUeA=="], + "dunder-proto": ["dunder-proto@1.0.1", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-errors": "^1.3.0", "gopd": "^1.2.0" } }, "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A=="], - "electron-to-chromium": ["electron-to-chromium@1.5.267", "", {}, "sha512-0Drusm6MVRXSOJpGbaSVgcQsuB4hEkMpHXaVstcPmhu5LIedxs1xNK/nIxmQIU/RPC0+1/o0AVZfBTkTNJOdUw=="], + "eastasianwidth": ["eastasianwidth@0.2.0", "", {}, "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA=="], + + "ejs": ["ejs@3.1.10", "", { "dependencies": { "jake": "^10.8.5" }, "bin": { "ejs": "bin/cli.js" } }, "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA=="], + + "electron": ["electron@41.1.1", "", { "dependencies": { "@electron/get": "^2.0.0", "@types/node": "^24.9.0", "extract-zip": "^2.0.1" }, "bin": { "electron": "cli.js" } }, "sha512-8bgvDhBjli+3Z2YCKgzzoBPh6391pr7Xv2h/tTJG4ETgvPvUxZomObbZLs31mUzYb6VrlcDDd9cyWyNKtPm3tA=="], + + "electron-builder": ["electron-builder@26.8.1", "", { "dependencies": { "app-builder-lib": "26.8.1", "builder-util": "26.8.1", "builder-util-runtime": "9.5.1", "chalk": "^4.1.2", "ci-info": "^4.2.0", "dmg-builder": "26.8.1", "fs-extra": "^10.1.0", "lazy-val": "^1.0.5", "simple-update-notifier": "2.0.0", "yargs": "^17.6.2" }, "bin": { "electron-builder": "cli.js", "install-app-deps": "install-app-deps.js" } }, "sha512-uWhx1r74NGpCagG0ULs/P9Nqv2nsoo+7eo4fLUOB8L8MdWltq9odW/uuLXMFCDGnPafknYLZgjNX0ZIFRzOQAw=="], + + "electron-builder-squirrel-windows": ["electron-builder-squirrel-windows@26.8.1", "", { "dependencies": { "app-builder-lib": "26.8.1", "builder-util": "26.8.1", "electron-winstaller": "5.4.0" } }, "sha512-o288fIdgPLHA76eDrFADHPoo7VyGkDCYbLV1GzndaMSAVBoZrGvM9m2IehdcVMzdAZJ2eV9bgyissQXHv5tGzA=="], + + "electron-publish": ["electron-publish@26.8.1", "", { "dependencies": { "@types/fs-extra": "^9.0.11", "builder-util": "26.8.1", "builder-util-runtime": "9.5.1", "chalk": "^4.1.2", "form-data": "^4.0.5", "fs-extra": "^10.1.0", "lazy-val": "^1.0.5", "mime": "^2.5.2" } }, "sha512-q+jrSTIh/Cv4eGZa7oVR+grEJo/FoLMYBAnSL5GCtqwUpr1T+VgKB/dn1pnzxIxqD8S/jP1yilT9VrwCqINR4w=="], + + "electron-to-chromium": ["electron-to-chromium@1.5.331", "", {}, "sha512-IbxXrsTlD3hRodkLnbxAPP4OuJYdWCeM3IOdT+CpcMoIwIoDfCmRpEtSPfwBXxVkg9xmBeY7Lz2Eo2TDn/HC3Q=="], + + "electron-updater": ["electron-updater@6.8.3", "", { "dependencies": { "builder-util-runtime": "9.5.1", "fs-extra": "^10.1.0", "js-yaml": "^4.1.0", "lazy-val": "^1.0.5", "lodash.escaperegexp": "^4.1.2", "lodash.isequal": "^4.5.0", "semver": "~7.7.3", "tiny-typed-emitter": "^2.1.0" } }, "sha512-Z6sgw3jgbikWKXei1ENdqFOxBP0WlXg3TtKfz0rgw2vIZFJUyI4pD7ZN7jrkm7EoMK+tcm/qTnPUdqfZukBlBQ=="], + + "electron-winstaller": ["electron-winstaller@5.4.0", "", { "dependencies": { "@electron/asar": "^3.2.1", "debug": "^4.1.1", "fs-extra": "^7.0.1", "lodash": "^4.17.21", "temp": "^0.9.0" }, "optionalDependencies": { "@electron/windows-sign": "^1.1.2" } }, "sha512-bO3y10YikuUwUuDUQRM4KfwNkKhnpVO7IPdbsrejwN9/AABJzzTQ4GeHwyzNSrVO+tEH3/Np255a3sVZpZDjvg=="], "emoji-regex": ["emoji-regex@9.2.2", "", {}, "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg=="], - "enhanced-resolve": ["enhanced-resolve@5.20.0", "", { "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.3.0" } }, "sha512-/ce7+jQ1PQ6rVXwe+jKEg5hW5ciicHwIQUagZkp6IufBoY3YDgdTTY1azVs0qoRgVmvsNB+rbjLJxDAeHHtwsQ=="], + "encoding": ["encoding@0.1.13", "", { "dependencies": { "iconv-lite": "^0.6.2" } }, "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A=="], + + "end-of-stream": ["end-of-stream@1.4.5", "", { "dependencies": { "once": "^1.4.0" } }, "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg=="], + + "enhanced-resolve": ["enhanced-resolve@5.20.1", "", { "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.3.0" } }, "sha512-Qohcme7V1inbAfvjItgw0EaxVX5q2rdVEZHRBrEQdRZTssLDGsL8Lwrznl8oQ/6kuTJONLaDcGjkNP247XEhcA=="], + + "env-paths": ["env-paths@2.2.1", "", {}, "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A=="], + + "err-code": ["err-code@2.0.3", "", {}, "sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA=="], "es-abstract": ["es-abstract@1.24.1", "", { "dependencies": { "array-buffer-byte-length": "^1.0.2", "arraybuffer.prototype.slice": "^1.0.4", "available-typed-arrays": "^1.0.7", "call-bind": "^1.0.8", "call-bound": "^1.0.4", "data-view-buffer": "^1.0.2", "data-view-byte-length": "^1.0.2", "data-view-byte-offset": "^1.0.1", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.1.1", "es-set-tostringtag": "^2.1.0", "es-to-primitive": "^1.3.0", "function.prototype.name": "^1.1.8", "get-intrinsic": "^1.3.0", "get-proto": "^1.0.1", "get-symbol-description": "^1.1.0", "globalthis": "^1.0.4", "gopd": "^1.2.0", "has-property-descriptors": "^1.0.2", "has-proto": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "internal-slot": "^1.1.0", "is-array-buffer": "^3.0.5", "is-callable": "^1.2.7", "is-data-view": "^1.0.2", "is-negative-zero": "^2.0.3", "is-regex": "^1.2.1", "is-set": "^2.0.3", "is-shared-array-buffer": "^1.0.4", "is-string": "^1.1.1", "is-typed-array": "^1.1.15", "is-weakref": "^1.1.1", "math-intrinsics": "^1.1.0", "object-inspect": "^1.13.4", "object-keys": "^1.1.1", "object.assign": "^4.1.7", "own-keys": "^1.0.1", "regexp.prototype.flags": "^1.5.4", "safe-array-concat": "^1.1.3", "safe-push-apply": "^1.0.0", "safe-regex-test": "^1.1.0", "set-proto": "^1.0.0", "stop-iteration-iterator": "^1.1.0", "string.prototype.trim": "^1.2.10", "string.prototype.trimend": "^1.0.9", "string.prototype.trimstart": "^1.0.8", "typed-array-buffer": "^1.0.3", "typed-array-byte-length": "^1.0.3", "typed-array-byte-offset": "^1.0.4", "typed-array-length": "^1.0.7", "unbox-primitive": "^1.1.0", "which-typed-array": "^1.1.19" } }, "sha512-zHXBLhP+QehSSbsS9Pt23Gg964240DPd6QCf8WpkqEXxQ7fhdZzYsocOr5u7apWonsS5EjZDmTF+/slGMyasvw=="], @@ -489,7 +680,7 @@ "es-errors": ["es-errors@1.3.0", "", {}, "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw=="], - "es-iterator-helpers": ["es-iterator-helpers@1.2.2", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.4", "define-properties": "^1.2.1", "es-abstract": "^1.24.1", "es-errors": "^1.3.0", "es-set-tostringtag": "^2.1.0", "function-bind": "^1.1.2", "get-intrinsic": "^1.3.0", "globalthis": "^1.0.4", "gopd": "^1.2.0", "has-property-descriptors": "^1.0.2", "has-proto": "^1.2.0", "has-symbols": "^1.1.0", "internal-slot": "^1.1.0", "iterator.prototype": "^1.1.5", "safe-array-concat": "^1.1.3" } }, "sha512-BrUQ0cPTB/IwXj23HtwHjS9n7O4h9FX94b4xc5zlTHxeLgTAdzYUDyy6KdExAl9lbN5rtfe44xpjpmj9grxs5w=="], + "es-iterator-helpers": ["es-iterator-helpers@1.3.1", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.4", "define-properties": "^1.2.1", "es-abstract": "^1.24.1", "es-errors": "^1.3.0", "es-set-tostringtag": "^2.1.0", "function-bind": "^1.1.2", "get-intrinsic": "^1.3.0", "globalthis": "^1.0.4", "gopd": "^1.2.0", "has-property-descriptors": "^1.0.2", "has-proto": "^1.2.0", "has-symbols": "^1.1.0", "internal-slot": "^1.1.0", "iterator.prototype": "^1.1.5", "math-intrinsics": "^1.1.0", "safe-array-concat": "^1.1.3" } }, "sha512-zWwRvqWiuBPr0muUG/78cW3aHROFCNIQ3zpmYDpwdbnt2m+xlNyRWpHBpa2lJjSBit7BQ+RXA1iwbSmu5yJ/EQ=="], "es-object-atoms": ["es-object-atoms@1.1.1", "", { "dependencies": { "es-errors": "^1.3.0" } }, "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA=="], @@ -499,15 +690,17 @@ "es-to-primitive": ["es-to-primitive@1.3.0", "", { "dependencies": { "is-callable": "^1.2.7", "is-date-object": "^1.0.5", "is-symbol": "^1.0.4" } }, "sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g=="], + "es6-error": ["es6-error@4.1.1", "", {}, "sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg=="], + "escalade": ["escalade@3.2.0", "", {}, "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="], "escape-string-regexp": ["escape-string-regexp@4.0.0", "", {}, "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA=="], - "eslint": ["eslint@10.1.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.2", "@eslint/config-array": "^0.23.3", "@eslint/config-helpers": "^0.5.3", "@eslint/core": "^1.1.1", "@eslint/plugin-kit": "^0.6.1", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", "@types/estree": "^1.0.6", "ajv": "^6.14.0", "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", "eslint-scope": "^9.1.2", "eslint-visitor-keys": "^5.0.1", "espree": "^11.2.0", "esquery": "^1.7.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^8.0.0", "find-up": "^5.0.0", "glob-parent": "^6.0.2", "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "json-stable-stringify-without-jsonify": "^1.0.1", "minimatch": "^10.2.4", "natural-compare": "^1.4.0", "optionator": "^0.9.3" }, "peerDependencies": { "jiti": "*" }, "optionalPeers": ["jiti"], "bin": { "eslint": "bin/eslint.js" } }, "sha512-S9jlY/ELKEUwwQnqWDO+f+m6sercqOPSqXM5Go94l7DOmxHVDgmSFGWEzeE/gwgTAr0W103BWt0QLe/7mabIvA=="], + "eslint": ["eslint@10.2.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.2", "@eslint/config-array": "^0.23.4", "@eslint/config-helpers": "^0.5.4", "@eslint/core": "^1.2.0", "@eslint/plugin-kit": "^0.7.0", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", "@types/estree": "^1.0.6", "ajv": "^6.14.0", "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", "eslint-scope": "^9.1.2", "eslint-visitor-keys": "^5.0.1", "espree": "^11.2.0", "esquery": "^1.7.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^8.0.0", "find-up": "^5.0.0", "glob-parent": "^6.0.2", "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "json-stable-stringify-without-jsonify": "^1.0.1", "minimatch": "^10.2.4", "natural-compare": "^1.4.0", "optionator": "^0.9.3" }, "peerDependencies": { "jiti": "*" }, "optionalPeers": ["jiti"], "bin": { "eslint": "bin/eslint.js" } }, "sha512-+L0vBFYGIpSNIt/KWTpFonPrqYvgKw1eUI5Vn7mEogrQcWtWYtNQ7dNqC+px/J0idT3BAkiWrhfS7k+Tum8TUA=="], "eslint-config-prettier": ["eslint-config-prettier@10.1.8", "", { "peerDependencies": { "eslint": ">=7.0.0" }, "bin": { "eslint-config-prettier": "bin/cli.js" } }, "sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w=="], - "eslint-import-resolver-node": ["eslint-import-resolver-node@0.3.9", "", { "dependencies": { "debug": "^3.2.7", "is-core-module": "^2.13.0", "resolve": "^1.22.4" } }, "sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g=="], + "eslint-import-resolver-node": ["eslint-import-resolver-node@0.3.10", "", { "dependencies": { "debug": "^3.2.7", "is-core-module": "^2.16.1", "resolve": "^2.0.0-next.6" } }, "sha512-tRrKqFyCaKict5hOd244sL6EQFNycnMQnBe+j8uqGNXYzsImGbGUU4ibtoaBmv5FLwJwcFJNeg1GeVjQfbMrDQ=="], "eslint-module-utils": ["eslint-module-utils@2.12.1", "", { "dependencies": { "debug": "^3.2.7" } }, "sha512-L8jSWTze7K2mTg0vos/RuLRS5soomksDPoJLXIslC7c8Wmut3bx7CPpJijDcBZtxQ5lrbUdM+s0OlNbz0DCDNw=="], @@ -535,6 +728,12 @@ "esutils": ["esutils@2.0.3", "", {}, "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g=="], + "exponential-backoff": ["exponential-backoff@3.1.3", "", {}, "sha512-ZgEeZXj30q+I0EN+CbSSpIyPaJ5HVQD18Z1m+u1FXbAeT94mr1zw50q4q6jiiC447Nl/YTcIYSAftiGqetwXCA=="], + + "extract-zip": ["extract-zip@2.0.1", "", { "dependencies": { "debug": "^4.1.1", "get-stream": "^5.1.0", "yauzl": "^2.10.0" }, "optionalDependencies": { "@types/yauzl": "^2.9.1" }, "bin": { "extract-zip": "cli.js" } }, "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg=="], + + "extsprintf": ["extsprintf@1.4.1", "", {}, "sha512-Wrk35e8ydCKDj/ArClo1VrPVmN8zph5V4AtHwIuHhvMXsKf73UT3BOD+azBIW+3wOJ4FhEH7zyaJCFvChjYvMA=="], + "fast-bmp": ["fast-bmp@4.0.1", "", { "dependencies": { "iobuffer": "^6.0.0" } }, "sha512-+KtMijJj+uA8Sl6EXAnhza7US7EXSY5Z9NeiJwT1wopVUksyLMXL5iFmn9FjY8FdkstOkpJI9RuEVXkGpIPSwg=="], "fast-deep-equal": ["fast-deep-equal@3.1.3", "", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="], @@ -553,6 +752,8 @@ "fastq": ["fastq@1.20.1", "", { "dependencies": { "reusify": "^1.0.4" } }, "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw=="], + "fd-slicer": ["fd-slicer@1.1.0", "", { "dependencies": { "pend": "~1.2.0" } }, "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g=="], + "fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="], "fflate": ["fflate@0.8.2", "", {}, "sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A=="], @@ -563,22 +764,32 @@ "file-type": ["file-type@10.11.0", "", {}, "sha512-uzk64HRpUZyTGZtVuvrjP0FYxzQrBf4rojot6J65YMEbwBLB0CWm0CLojVpwpmFmxcE/lkvYICgfcGozbBq6rw=="], + "filelist": ["filelist@1.0.6", "", { "dependencies": { "minimatch": "^5.0.1" } }, "sha512-5giy2PkLYY1cP39p17Ech+2xlpTRL9HLspOfEgm0L6CwBXBTgsK5ou0JtzYuepxkaQ/tvhCFIJ5uXo0OrM2DxA=="], + "fill-range": ["fill-range@7.1.1", "", { "dependencies": { "to-regex-range": "^5.0.1" } }, "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg=="], "find-up": ["find-up@5.0.0", "", { "dependencies": { "locate-path": "^6.0.0", "path-exists": "^4.0.0" } }, "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng=="], "flat-cache": ["flat-cache@4.0.1", "", { "dependencies": { "flatted": "^3.2.9", "keyv": "^4.5.4" } }, "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw=="], - "flatted": ["flatted@3.3.3", "", {}, "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg=="], + "flatted": ["flatted@3.4.2", "", {}, "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA=="], "follow-redirects": ["follow-redirects@1.15.11", "", {}, "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ=="], "for-each": ["for-each@0.3.5", "", { "dependencies": { "is-callable": "^1.2.7" } }, "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg=="], + "foreground-child": ["foreground-child@3.3.1", "", { "dependencies": { "cross-spawn": "^7.0.6", "signal-exit": "^4.0.1" } }, "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw=="], + + "form-data": ["form-data@4.0.5", "", { "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", "es-set-tostringtag": "^2.1.0", "hasown": "^2.0.2", "mime-types": "^2.1.12" } }, "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w=="], + "fraction.js": ["fraction.js@5.3.4", "", {}, "sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ=="], "framer-motion": ["framer-motion@12.38.0", "", { "dependencies": { "motion-dom": "^12.38.0", "motion-utils": "^12.36.0", "tslib": "^2.4.0" }, "peerDependencies": { "@emotion/is-prop-valid": "*", "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0" }, "optionalPeers": ["@emotion/is-prop-valid", "react", "react-dom"] }, "sha512-rFYkY/pigbcswl1XQSb7q424kSTQ8q6eAC+YUsSKooHQYuLdzdHjrt6uxUC+PRAO++q5IS7+TamgIw1AphxR+g=="], + "fs-extra": ["fs-extra@10.1.0", "", { "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", "universalify": "^2.0.0" } }, "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ=="], + + "fs-minipass": ["fs-minipass@3.0.3", "", { "dependencies": { "minipass": "^7.0.3" } }, "sha512-XUBA9XClHbnJWSfBzjkm6RvPsyg3sryZt06BEQoXcF7EK/xpGaQYJgQKDJSUH5SGZ76Y7pFx1QBnXz09rU5Fbw=="], + "fs.realpath": ["fs.realpath@1.0.0", "", {}, "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw=="], "fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="], @@ -593,16 +804,22 @@ "gensync": ["gensync@1.0.0-beta.2", "", {}, "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg=="], + "get-caller-file": ["get-caller-file@2.0.5", "", {}, "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg=="], + "get-intrinsic": ["get-intrinsic@1.3.0", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.1.1", "function-bind": "^1.1.2", "get-proto": "^1.0.1", "gopd": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "math-intrinsics": "^1.1.0" } }, "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ=="], "get-proto": ["get-proto@1.0.1", "", { "dependencies": { "dunder-proto": "^1.0.1", "es-object-atoms": "^1.0.0" } }, "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g=="], + "get-stream": ["get-stream@5.2.0", "", { "dependencies": { "pump": "^3.0.0" } }, "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA=="], + "get-symbol-description": ["get-symbol-description@1.1.0", "", { "dependencies": { "call-bound": "^1.0.3", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.6" } }, "sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg=="], "glob": ["glob@13.0.6", "", { "dependencies": { "minimatch": "^10.2.2", "minipass": "^7.1.3", "path-scurry": "^2.0.2" } }, "sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw=="], "glob-parent": ["glob-parent@6.0.2", "", { "dependencies": { "is-glob": "^4.0.3" } }, "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A=="], + "global-agent": ["global-agent@3.0.0", "", { "dependencies": { "boolean": "^3.0.1", "es6-error": "^4.1.1", "matcher": "^3.0.0", "roarr": "^2.15.3", "semver": "^7.3.2", "serialize-error": "^7.0.1" } }, "sha512-PT6XReJ+D07JvGoxQMkT6qji/jVNfX/h364XHZOWeRzy64sSFr+xJ5OX7LI3b4MPQzdL4H8Y8M0xzPpsVMwA8Q=="], + "globals": ["globals@17.4.0", "", {}, "sha512-hjrNztw/VajQwOLsMNT1cbJiH2muO3OROCHnbehc8eY5JyD2gqz4AcMHPqgaOR59DjgUjYAYLeH699g/eWi2jw=="], "globalthis": ["globalthis@1.0.4", "", { "dependencies": { "define-properties": "^1.2.1", "gopd": "^1.0.1" } }, "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ=="], @@ -611,6 +828,8 @@ "gopd": ["gopd@1.2.0", "", {}, "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg=="], + "got": ["got@11.8.6", "", { "dependencies": { "@sindresorhus/is": "^4.0.0", "@szmarczak/http-timer": "^4.0.5", "@types/cacheable-request": "^6.0.1", "@types/responselike": "^1.0.0", "cacheable-lookup": "^5.0.3", "cacheable-request": "^7.0.2", "decompress-response": "^6.0.0", "http2-wrapper": "^1.0.0-beta.5.2", "lowercase-keys": "^2.0.0", "p-cancelable": "^2.0.0", "responselike": "^2.0.0" } }, "sha512-6tfZ91bOr7bOXnK7PRDCGBLa1H4U080YHNaAQ2KsMGlLEzRbk44nsZF2E1IeRc3vtJHPVbKCYgdFbaGO2ljd8g=="], + "graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="], "graphemer": ["graphemer@1.4.0", "", {}, "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag=="], @@ -635,15 +854,29 @@ "hermes-parser": ["hermes-parser@0.25.1", "", { "dependencies": { "hermes-estree": "0.25.1" } }, "sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA=="], + "hosted-git-info": ["hosted-git-info@4.1.0", "", { "dependencies": { "lru-cache": "^6.0.0" } }, "sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA=="], + "html-parse-stringify": ["html-parse-stringify@3.0.1", "", { "dependencies": { "void-elements": "3.1.0" } }, "sha512-KknJ50kTInJ7qIScF3jeaFRpMpE8/lfiTdzf/twXyPBLAGrLRTmkz3AdTnKeh40X8k9L2fdYwEp/42WGXIRGcg=="], + "http-cache-semantics": ["http-cache-semantics@4.2.0", "", {}, "sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ=="], + + "http-proxy-agent": ["http-proxy-agent@7.0.2", "", { "dependencies": { "agent-base": "^7.1.0", "debug": "^4.3.4" } }, "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig=="], + + "http2-wrapper": ["http2-wrapper@1.0.3", "", { "dependencies": { "quick-lru": "^5.1.1", "resolve-alpn": "^1.0.0" } }, "sha512-V+23sDMr12Wnz7iTcDeJr3O6AIxlnvT/bmaAAAP/Xda35C90p9599p0F1eHR/N1KILWSoWVAiOMFjBBXaXSMxg=="], + "https-proxy-agent": ["https-proxy-agent@7.0.6", "", { "dependencies": { "agent-base": "^7.1.2", "debug": "4" } }, "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw=="], - "i18next": ["i18next@26.0.1", "", { "dependencies": { "@babel/runtime": "^7.29.2" }, "peerDependencies": { "typescript": "^5 || ^6" }, "optionalPeers": ["typescript"] }, "sha512-vtz5sXU4+nkCm8yEU+JJ6yYIx0mkg9e68W0G0PXpnOsmzLajNsW5o28DJMqbajxfsfq0gV3XdrBudsDQnwxfsQ=="], + "i18next": ["i18next@26.0.3", "", { "dependencies": { "@babel/runtime": "^7.29.2" }, "peerDependencies": { "typescript": "^5 || ^6" }, "optionalPeers": ["typescript"] }, "sha512-1571kXINxHKY7LksWp8wP+zP0YqHSSpl/OW0Y0owFEf2H3s8gCAffWaZivcz14rMkOvn3R/psiQxVsR9t2Nafg=="], + + "iconv-corefoundation": ["iconv-corefoundation@1.1.7", "", { "dependencies": { "cli-truncate": "^2.1.0", "node-addon-api": "^1.6.3" }, "os": "darwin" }, "sha512-T10qvkw0zz4wnm560lOEg0PovVqUXuOFhhHAkixw8/sycy7TJt7v/RrkEKEQnAw2viPSJu6iAkErxnzR0g8PpQ=="], + + "iconv-lite": ["iconv-lite@0.6.3", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw=="], + + "ieee754": ["ieee754@1.2.1", "", {}, "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="], "ignore": ["ignore@5.3.2", "", {}, "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="], - "image-js": ["image-js@1.4.0", "", { "dependencies": { "bresenham-zingl": "^0.2.5", "colord": "^2.9.3", "fast-bmp": "^4.0.1", "fast-jpeg": "^3.0.1", "fast-png": "^8.0.0", "image-type": "^4.1.0", "jpeg-js": "^0.4.4", "js-priority-queue": "^0.1.5", "median-quickselect": "^1.0.1", "ml-affine-transform": "^1.0.3", "ml-convolution": "^2.0.0", "ml-matrix": "^6.12.1", "ml-ransac": "^1.0.0", "ml-regression-multivariate-linear": "^2.0.4", "ml-regression-polynomial-2d": "^1.0.0", "ml-spectra-processing": "^14.18.2", "robust-point-in-polygon": "^1.0.3", "skia-canvas": "^3.0.8", "ssim.js": "^3.5.0", "tiff": "^7.1.3", "ts-pattern": "^5.9.0", "uint8-base64": "^1.0.0" } }, "sha512-X3CGc5t936NUME2yfJvEyvkVHc5Kbwvt6VxufIQf5o53nlRBxp1OpatdxDdvRPkArqUw3qRA1HxOIvUE/nniXA=="], + "image-js": ["image-js@1.5.0", "", { "dependencies": { "bresenham-zingl": "^0.2.5", "colord": "^2.9.3", "fast-bmp": "^4.0.1", "fast-jpeg": "^3.0.1", "fast-png": "^8.0.0", "image-type": "^4.1.0", "jpeg-js": "^0.4.4", "js-priority-queue": "^0.1.5", "median-quickselect": "^1.0.1", "ml-affine-transform": "^1.0.3", "ml-convolution": "^2.0.0", "ml-matrix": "^6.12.1", "ml-ransac": "^1.0.0", "ml-regression-multivariate-linear": "^2.0.4", "ml-regression-polynomial-2d": "^1.0.0", "ml-spectra-processing": "^14.18.2", "robust-point-in-polygon": "^1.0.3", "ssim.js": "^3.5.0", "tiff": "^7.1.3", "ts-pattern": "^5.9.0", "uint8-base64": "^1.0.0" }, "optionalDependencies": { "skia-canvas": "^3.0.8" } }, "sha512-ZE/e7wlb5732r3wHXcPtRiirODk/YKYBKzpffd3qfYD1wUA0PrYewYOHLOJGQoi1vj6G9TAQpHIkxV91qm5FBQ=="], "image-type": ["image-type@4.1.0", "", { "dependencies": { "file-type": "^10.10.0" } }, "sha512-CFJMJ8QK8lJvRlTCEgarL4ro6hfDQKif2HjSvYCdQZESaIPV4v9imrf7BQHK+sQeTeNeMpWciR9hyC/g8ybXEg=="], @@ -661,6 +894,8 @@ "iobuffer": ["iobuffer@6.0.1", "", {}, "sha512-SZWYkWNfjIXIBYSDpXDYIgshqtbOPsi4lviawAEceR1Kqk+sHDlcQjWrzNQsii80AyBY0q5c8HCTNjqo74ul+Q=="], + "ip-address": ["ip-address@10.1.0", "", {}, "sha512-XXADHxXmvT9+CRxhXg56LJovE+bmWnEWB78LB83VZTprKTmaC5QfruXocxzTZ2Kl0DNwKuBdlIhjL8LeY8Sf8Q=="], + "is-any-array": ["is-any-array@2.0.1", "", {}, "sha512-UtilS7hLRu++wb/WBAw9bNuP1Eg04Ivn1vERJck8zJthEvXCBEBpGR/33u/xLKWEQf95803oalHrVDptcAvFdQ=="], "is-array-buffer": ["is-array-buffer@3.0.5", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.3", "get-intrinsic": "^1.2.6" } }, "sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A=="], @@ -683,10 +918,14 @@ "is-finalizationregistry": ["is-finalizationregistry@1.1.1", "", { "dependencies": { "call-bound": "^1.0.3" } }, "sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg=="], + "is-fullwidth-code-point": ["is-fullwidth-code-point@3.0.0", "", {}, "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="], + "is-generator-function": ["is-generator-function@1.1.2", "", { "dependencies": { "call-bound": "^1.0.4", "generator-function": "^2.0.0", "get-proto": "^1.0.1", "has-tostringtag": "^1.0.2", "safe-regex-test": "^1.1.0" } }, "sha512-upqt1SkGkODW9tsGNG5mtXTXtECizwtS2kA161M+gJPc1xdb/Ax629af6YrTwcOeQHbewrPNlE5Dx7kzvXTizA=="], "is-glob": ["is-glob@4.0.3", "", { "dependencies": { "is-extglob": "^2.1.1" } }, "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg=="], + "is-interactive": ["is-interactive@1.0.0", "", {}, "sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w=="], + "is-map": ["is-map@2.0.3", "", {}, "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw=="], "is-negative-zero": ["is-negative-zero@2.0.3", "", {}, "sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw=="], @@ -709,6 +948,8 @@ "is-typed-array": ["is-typed-array@1.1.15", "", { "dependencies": { "which-typed-array": "^1.1.16" } }, "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ=="], + "is-unicode-supported": ["is-unicode-supported@0.1.0", "", {}, "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw=="], + "is-weakmap": ["is-weakmap@2.0.2", "", {}, "sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w=="], "is-weakref": ["is-weakref@1.1.1", "", { "dependencies": { "call-bound": "^1.0.3" } }, "sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew=="], @@ -717,10 +958,16 @@ "isarray": ["isarray@2.0.5", "", {}, "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw=="], - "isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="], + "isbinaryfile": ["isbinaryfile@5.0.7", "", {}, "sha512-gnWD14Jh3FzS3CPhF0AxNOJ8CxqeblPTADzI38r0wt8ZyQl5edpy75myt08EG2oKvpyiqSqsx+Wkz9vtkbTqYQ=="], + + "isexe": ["isexe@3.1.5", "", {}, "sha512-6B3tLtFqtQS4ekarvLVMZ+X+VlvQekbe4taUkf/rhVO3d/h0M2rfARm/pXLcPEsjjMsFgrFgSrhQIxcSVrBz8w=="], "iterator.prototype": ["iterator.prototype@1.1.5", "", { "dependencies": { "define-data-property": "^1.1.4", "es-object-atoms": "^1.0.0", "get-intrinsic": "^1.2.6", "get-proto": "^1.0.0", "has-symbols": "^1.1.0", "set-function-name": "^2.0.2" } }, "sha512-H0dkQoCa3b2VEeKQBOxFph+JAbcrQdE7KC0UkqwpLmv2EC4P41QXP+rqo9wYodACiG5/WM5s9oDApTU8utwj9g=="], + "jackspeak": ["jackspeak@3.4.3", "", { "dependencies": { "@isaacs/cliui": "^8.0.2" }, "optionalDependencies": { "@pkgjs/parseargs": "^0.11.0" } }, "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw=="], + + "jake": ["jake@10.9.4", "", { "dependencies": { "async": "^3.2.6", "filelist": "^1.0.4", "picocolors": "^1.1.1" }, "bin": { "jake": "bin/cli.js" } }, "sha512-wpHYzhxiVQL+IV05BLE2Xn34zW1S223hvjtqk0+gsPrwd/8JNLXJgZZM/iPFsYc1xyphF+6M6EvdE5E9MBGkDA=="], + "jiti": ["jiti@2.6.1", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ=="], "jpeg-js": ["jpeg-js@0.4.4", "", {}, "sha512-WZzeDOEtTOBK4Mdsar0IqEU5sMr3vSV2RqkAIzUEV2BHnUfKGyswWFPFwK5EeDo93K3FohSHbLAjj0s1Wzd+dg=="], @@ -729,7 +976,7 @@ "js-tokens": ["js-tokens@4.0.0", "", {}, "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="], - "js-yaml": ["js-yaml@4.1.0", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA=="], + "js-yaml": ["js-yaml@4.1.1", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA=="], "jsesc": ["jsesc@3.1.0", "", { "bin": { "jsesc": "bin/jsesc" } }, "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA=="], @@ -739,7 +986,11 @@ "json-stable-stringify-without-jsonify": ["json-stable-stringify-without-jsonify@1.0.1", "", {}, "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw=="], - "json5": ["json5@1.0.2", "", { "dependencies": { "minimist": "^1.2.0" }, "bin": { "json5": "lib/cli.js" } }, "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA=="], + "json-stringify-safe": ["json-stringify-safe@5.0.1", "", {}, "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA=="], + + "json5": ["json5@2.2.3", "", { "bin": { "json5": "lib/cli.js" } }, "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg=="], + + "jsonfile": ["jsonfile@6.2.0", "", { "dependencies": { "universalify": "^2.0.0" }, "optionalDependencies": { "graceful-fs": "^4.1.6" } }, "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg=="], "jsx-ast-utils": ["jsx-ast-utils@3.3.5", "", { "dependencies": { "array-includes": "^3.1.6", "array.prototype.flat": "^1.3.1", "object.assign": "^4.1.4", "object.values": "^1.1.6" } }, "sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ=="], @@ -749,6 +1000,8 @@ "language-tags": ["language-tags@1.0.9", "", { "dependencies": { "language-subtag-registry": "^0.3.20" } }, "sha512-MbjN408fEndfiQXbFQ1vnd+1NoLDsnQW41410oQBXiyXDMYH5z505juWa4KUE1LqxRC7DgOgZDbKLxHIwm27hA=="], + "lazy-val": ["lazy-val@1.0.5", "", {}, "sha512-0/BnGCCfyUMkBpeDgWihanIAF9JmZhHBgUhEqzvf+adhNGLoP6TaiI5oF8oyb3I45P+PcnrqihSf01M0l0G5+Q=="], + "levn": ["levn@0.4.1", "", { "dependencies": { "prelude-ls": "^1.2.1", "type-check": "~0.4.0" } }, "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ=="], "lightningcss": ["lightningcss@1.32.0", "", { "dependencies": { "detect-libc": "^2.0.3" }, "optionalDependencies": { "lightningcss-android-arm64": "1.32.0", "lightningcss-darwin-arm64": "1.32.0", "lightningcss-darwin-x64": "1.32.0", "lightningcss-freebsd-x64": "1.32.0", "lightningcss-linux-arm-gnueabihf": "1.32.0", "lightningcss-linux-arm64-gnu": "1.32.0", "lightningcss-linux-arm64-musl": "1.32.0", "lightningcss-linux-x64-gnu": "1.32.0", "lightningcss-linux-x64-musl": "1.32.0", "lightningcss-win32-arm64-msvc": "1.32.0", "lightningcss-win32-x64-msvc": "1.32.0" } }, "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ=="], @@ -777,20 +1030,32 @@ "locate-path": ["locate-path@6.0.0", "", { "dependencies": { "p-locate": "^5.0.0" } }, "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw=="], - "lodash": ["lodash@4.17.23", "", {}, "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w=="], + "lodash": ["lodash@4.18.1", "", {}, "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q=="], + + "lodash.escaperegexp": ["lodash.escaperegexp@4.1.2", "", {}, "sha512-TM9YBvyC84ZxE3rgfefxUWiQKLilstD6k7PTGt6wfbtXF8ixIJLOL3VYyV/z+ZiPLsVxAsKAFVwWlWeb2Y8Yyw=="], + + "lodash.isequal": ["lodash.isequal@4.5.0", "", {}, "sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ=="], "lodash.merge": ["lodash.merge@4.6.2", "", {}, "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ=="], + "log-symbols": ["log-symbols@4.1.0", "", { "dependencies": { "chalk": "^4.1.0", "is-unicode-supported": "^0.1.0" } }, "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg=="], + "loglevel": ["loglevel@1.9.2", "", {}, "sha512-HgMmCqIJSAKqo68l0rS2AanEWfkxaZ5wNiEFb5ggm08lDs9Xl2KxBlX3PTcaD2chBM1gXAYf491/M2Rv8Jwayg=="], "loglevel-colored-level-prefix": ["loglevel-colored-level-prefix@1.0.0", "", { "dependencies": { "chalk": "^1.1.3", "loglevel": "^1.4.1" } }, "sha512-u45Wcxxc+SdAlh4yeF/uKlC1SPUPCy0gullSNKXod5I4bmifzk+Q4lSLExNEVn19tGaJipbZ4V4jbFn79/6mVA=="], "loose-envify": ["loose-envify@1.4.0", "", { "dependencies": { "js-tokens": "^3.0.0 || ^4.0.0" }, "bin": { "loose-envify": "cli.js" } }, "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q=="], + "lowercase-keys": ["lowercase-keys@2.0.0", "", {}, "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA=="], + "lru-cache": ["lru-cache@11.2.7", "", {}, "sha512-aY/R+aEsRelme17KGQa/1ZSIpLpNYYrhcrepKTZgE+W3WM16YMCaPwOHLHsmopZHELU0Ojin1lPVxKR0MihncA=="], "magic-string": ["magic-string@0.30.21", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="], + "make-fetch-happen": ["make-fetch-happen@14.0.3", "", { "dependencies": { "@npmcli/agent": "^3.0.0", "cacache": "^19.0.1", "http-cache-semantics": "^4.1.1", "minipass": "^7.0.2", "minipass-fetch": "^4.0.0", "minipass-flush": "^1.0.5", "minipass-pipeline": "^1.2.4", "negotiator": "^1.0.0", "proc-log": "^5.0.0", "promise-retry": "^2.0.1", "ssri": "^12.0.0" } }, "sha512-QMjGbFTP0blj97EeidG5hk/QhKQ3T4ICckQGLgz38QF7Vgbk6e6FTARN8KhKxyBbWn8R0HU+bnw8aSoFPD4qtQ=="], + + "matcher": ["matcher@3.0.0", "", { "dependencies": { "escape-string-regexp": "^4.0.0" } }, "sha512-OkeDaAZ/bQCxeFAozM55PKcKU0yJMPGifLwV4Qgjitu+5MoAfSQN4lsLJeXZ1b8w0x+/Emda6MZgXS1jvsapng=="], + "math-intrinsics": ["math-intrinsics@1.1.0", "", {}, "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g=="], "mdn-data": ["mdn-data@2.23.0", "", {}, "sha512-786vq1+4079JSeu2XdcDjrhi/Ry7BWtjDl9WtGPWLiIHb2T66GvIVflZTBoSNZ5JqTtJGYEVMuFA/lbQlMOyDQ=="], @@ -801,12 +1066,36 @@ "micromatch": ["micromatch@4.0.8", "", { "dependencies": { "braces": "^3.0.3", "picomatch": "^2.3.1" } }, "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA=="], - "minimatch": ["minimatch@10.2.4", "", { "dependencies": { "brace-expansion": "^5.0.2" } }, "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg=="], + "mime": ["mime@2.6.0", "", { "bin": { "mime": "cli.js" } }, "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg=="], + + "mime-db": ["mime-db@1.52.0", "", {}, "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="], + + "mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="], + + "mimic-fn": ["mimic-fn@2.1.0", "", {}, "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg=="], + + "mimic-response": ["mimic-response@3.1.0", "", {}, "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ=="], + + "minimatch": ["minimatch@10.2.5", "", { "dependencies": { "brace-expansion": "^5.0.5" } }, "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg=="], "minimist": ["minimist@1.2.8", "", {}, "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA=="], "minipass": ["minipass@7.1.3", "", {}, "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A=="], + "minipass-collect": ["minipass-collect@2.0.1", "", { "dependencies": { "minipass": "^7.0.3" } }, "sha512-D7V8PO9oaz7PWGLbCACuI1qEOsq7UKfLotx/C0Aet43fCUB/wfQ7DYeq2oR/svFJGYDHPr38SHATeaj/ZoKHKw=="], + + "minipass-fetch": ["minipass-fetch@4.0.1", "", { "dependencies": { "minipass": "^7.0.3", "minipass-sized": "^1.0.3", "minizlib": "^3.0.1" }, "optionalDependencies": { "encoding": "^0.1.13" } }, "sha512-j7U11C5HXigVuutxebFadoYBbd7VSdZWggSe64NVdvWNBqGAiXPL2QVCehjmw7lY1oF9gOllYbORh+hiNgfPgQ=="], + + "minipass-flush": ["minipass-flush@1.0.7", "", { "dependencies": { "minipass": "^3.0.0" } }, "sha512-TbqTz9cUwWyHS2Dy89P3ocAGUGxKjjLuR9z8w4WUTGAVgEj17/4nhgo2Du56i0Fm3Pm30g4iA8Lcqctc76jCzA=="], + + "minipass-pipeline": ["minipass-pipeline@1.2.4", "", { "dependencies": { "minipass": "^3.0.0" } }, "sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A=="], + + "minipass-sized": ["minipass-sized@1.0.3", "", { "dependencies": { "minipass": "^3.0.0" } }, "sha512-MbkQQ2CTiBMlA2Dm/5cY+9SWFEN8pzzOXi6rlM5Xxq0Yqbda5ZQy9sU75a673FE9ZK0Zsbr6Y5iP6u9nktfg2g=="], + + "minizlib": ["minizlib@3.1.0", "", { "dependencies": { "minipass": "^7.1.2" } }, "sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw=="], + + "mkdirp": ["mkdirp@0.5.6", "", { "dependencies": { "minimist": "^1.2.6" }, "bin": { "mkdirp": "bin/cmd.js" } }, "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw=="], + "ml-affine-transform": ["ml-affine-transform@1.0.3", "", { "dependencies": { "ml-matrix": "^6.10.4" } }, "sha512-uv13li9HOGuMdSKL+QEdaV25emO9kt17LzGCD+FLQrBsrU8p/r/3kS/GJ7xI+f3/aWghkBK733x0yBG6EwKFJA=="], "ml-array-max": ["ml-array-max@1.2.4", "", { "dependencies": { "is-any-array": "^2.0.0" } }, "sha512-BlEeg80jI0tW6WaPyGxf5Sa4sqvcyY6lbSn5Vcv44lp1I2GR6AWojfUvLnGTNsIXrZ8uqWmo8VcG1WpkI2ONMQ=="], @@ -847,9 +1136,25 @@ "natural-compare": ["natural-compare@1.4.0", "", {}, "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw=="], + "negotiator": ["negotiator@1.0.0", "", {}, "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg=="], + "next-power-of-two": ["next-power-of-two@1.0.0", "", {}, "sha512-+z6QY1SxkDk6CQJAeaIZKmcNubBCRP7J8DMQUBglz/sSkNsZoJ1kULjqk9skNPPplzs4i9PFhYrvNDdtQleF/A=="], - "node-releases": ["node-releases@2.0.27", "", {}, "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA=="], + "node-abi": ["node-abi@4.28.0", "", { "dependencies": { "semver": "^7.6.3" } }, "sha512-Qfp5XZL1cJDOabOT8H5gnqMTmM4NjvYzHp4I/Kt/Sl76OVkOBBHRFlPspGV0hYvMoqQsypFjT/Yp7Km0beXW9g=="], + + "node-addon-api": ["node-addon-api@1.7.2", "", {}, "sha512-ibPK3iA+vaY1eEjESkQkM0BbCqFOaZMiXRTtdB0u7b4djtY6JnsjvPdUHVMg6xQt3B8fpTTWHI9A+ADjM9frzg=="], + + "node-api-version": ["node-api-version@0.2.1", "", { "dependencies": { "semver": "^7.3.5" } }, "sha512-2xP/IGGMmmSQpI1+O/k72jF/ykvZ89JeuKX3TLJAYPDVLUalrshrLHkeVcCCZqG/eEa635cr8IBYzgnDvM2O8Q=="], + + "node-exports-info": ["node-exports-info@1.6.0", "", { "dependencies": { "array.prototype.flatmap": "^1.3.3", "es-errors": "^1.3.0", "object.entries": "^1.1.9", "semver": "^6.3.1" } }, "sha512-pyFS63ptit/P5WqUkt+UUfe+4oevH+bFeIiPPdfb0pFeYEu/1ELnJu5l+5EcTKYL5M7zaAa7S8ddywgXypqKCw=="], + + "node-gyp": ["node-gyp@11.5.0", "", { "dependencies": { "env-paths": "^2.2.0", "exponential-backoff": "^3.1.1", "graceful-fs": "^4.2.6", "make-fetch-happen": "^14.0.3", "nopt": "^8.0.0", "proc-log": "^5.0.0", "semver": "^7.3.5", "tar": "^7.4.3", "tinyglobby": "^0.2.12", "which": "^5.0.0" }, "bin": { "node-gyp": "bin/node-gyp.js" } }, "sha512-ra7Kvlhxn5V9Slyus0ygMa2h+UqExPqUIkfk7Pc8QTLT956JLSy51uWFwHtIYy0vI8cB4BDhc/S03+880My/LQ=="], + + "node-releases": ["node-releases@2.0.37", "", {}, "sha512-1h5gKZCF+pO/o3Iqt5Jp7wc9rH3eJJ0+nh/CIoiRwjRxde/hAHyLPXYN4V3CqKAbiZPSeJFSWHmJsbkicta0Eg=="], + + "nopt": ["nopt@8.1.0", "", { "dependencies": { "abbrev": "^3.0.0" }, "bin": { "nopt": "bin/nopt.js" } }, "sha512-ieGu42u/Qsa4TFktmaKEwM6MQH0pOWnaB3htzh0JRtx84+Mebc0cbZYN5bC+6WTZ4+77xrL9Pn5m7CV6VIkV7A=="], + + "normalize-url": ["normalize-url@6.1.0", "", {}, "sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A=="], "object-assign": ["object-assign@4.1.1", "", {}, "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg=="], @@ -869,14 +1174,24 @@ "once": ["once@1.4.0", "", { "dependencies": { "wrappy": "1" } }, "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w=="], + "onetime": ["onetime@5.1.2", "", { "dependencies": { "mimic-fn": "^2.1.0" } }, "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg=="], + "optionator": ["optionator@0.9.4", "", { "dependencies": { "deep-is": "^0.1.3", "fast-levenshtein": "^2.0.6", "levn": "^0.4.1", "prelude-ls": "^1.2.1", "type-check": "^0.4.0", "word-wrap": "^1.2.5" } }, "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g=="], + "ora": ["ora@5.4.1", "", { "dependencies": { "bl": "^4.1.0", "chalk": "^4.1.0", "cli-cursor": "^3.1.0", "cli-spinners": "^2.5.0", "is-interactive": "^1.0.0", "is-unicode-supported": "^0.1.0", "log-symbols": "^4.1.0", "strip-ansi": "^6.0.0", "wcwidth": "^1.0.1" } }, "sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ=="], + "own-keys": ["own-keys@1.0.1", "", { "dependencies": { "get-intrinsic": "^1.2.6", "object-keys": "^1.1.1", "safe-push-apply": "^1.0.0" } }, "sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg=="], + "p-cancelable": ["p-cancelable@2.1.1", "", {}, "sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg=="], + "p-limit": ["p-limit@3.1.0", "", { "dependencies": { "yocto-queue": "^0.1.0" } }, "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ=="], "p-locate": ["p-locate@5.0.0", "", { "dependencies": { "p-limit": "^3.0.2" } }, "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw=="], + "p-map": ["p-map@7.0.4", "", {}, "sha512-tkAQEw8ysMzmkhgw8k+1U/iPhWNhykKnSk4Rd5zLoPJCuJaGRPo6YposrZgaxHKzDHdDWWZvE/Sk7hsL2X/CpQ=="], + + "package-json-from-dist": ["package-json-from-dist@1.0.1", "", {}, "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw=="], + "parent-module": ["parent-module@1.0.1", "", { "dependencies": { "callsites": "^3.0.0" } }, "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g=="], "parenthesis": ["parenthesis@3.1.8", "", {}, "sha512-KF/U8tk54BgQewkJPvB4s/US3VQY68BRDpH638+7O/n58TpnwiwnOtGIOsT2/i+M78s61BBpeC83STB88d8sqw=="], @@ -893,16 +1208,24 @@ "path-type": ["path-type@4.0.0", "", {}, "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw=="], + "pe-library": ["pe-library@0.4.1", "", {}, "sha512-eRWB5LBz7PpDu4PUlwT0PhnQfTQJlDDdPa35urV4Osrm0t0AqQFGn+UIkU3klZvwJ8KPO3VbBFsXquA6p6kqZw=="], + + "pend": ["pend@1.2.0", "", {}, "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg=="], + "picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="], "picomatch": ["picomatch@4.0.4", "", {}, "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A=="], + "plist": ["plist@3.1.0", "", { "dependencies": { "@xmldom/xmldom": "^0.8.8", "base64-js": "^1.5.1", "xmlbuilder": "^15.1.1" } }, "sha512-uysumyrvkUX0rX/dEVqt8gC3sTBzd4zoWfLeS29nb53imdaXVvLINYXTI2GNqzaMuvacNx4uJQ8+b3zXR0pkgQ=="], + "possible-typed-array-names": ["possible-typed-array-names@1.1.0", "", {}, "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg=="], "postcss": ["postcss@8.5.8", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg=="], "postcss-value-parser": ["postcss-value-parser@4.2.0", "", {}, "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ=="], + "postject": ["postject@1.0.0-alpha.6", "", { "dependencies": { "commander": "^9.4.0" }, "bin": { "postject": "dist/cli.js" } }, "sha512-b9Eb8h2eVqNE8edvKdwqkrY6O7kAwmI8kcnBv1NScolYJbo59XUF0noFq+lxbC1yN20bmC0WBEbDC5H/7ASb0A=="], + "prelude-ls": ["prelude-ls@1.2.1", "", {}, "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g=="], "prettier": ["prettier@3.8.1", "", { "bin": { "prettier": "bin/prettier.cjs" } }, "sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg=="], @@ -913,36 +1236,68 @@ "pretty-format": ["pretty-format@29.7.0", "", { "dependencies": { "@jest/schemas": "^29.6.3", "ansi-styles": "^5.0.0", "react-is": "^18.0.0" } }, "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ=="], + "proc-log": ["proc-log@5.0.0", "", {}, "sha512-Azwzvl90HaF0aCz1JrDdXQykFakSSNPaPoiZ9fm5qJIMHioDZEi7OAdRwSm6rSoPtY3Qutnm3L7ogmg3dc+wbQ=="], + + "progress": ["progress@2.0.3", "", {}, "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA=="], + + "promise-retry": ["promise-retry@2.0.1", "", { "dependencies": { "err-code": "^2.0.2", "retry": "^0.12.0" } }, "sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g=="], + "prop-types": ["prop-types@15.8.1", "", { "dependencies": { "loose-envify": "^1.4.0", "object-assign": "^4.1.1", "react-is": "^16.13.1" } }, "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg=="], + "proper-lockfile": ["proper-lockfile@4.1.2", "", { "dependencies": { "graceful-fs": "^4.2.4", "retry": "^0.12.0", "signal-exit": "^3.0.2" } }, "sha512-TjNPblN4BwAWMXU8s9AEz4JmQxnD1NNL7bNOY/AKUzyamc379FWASUhc/K1pL2noVb+XmZKLL68cjzLsiOAMaA=="], + + "pump": ["pump@3.0.4", "", { "dependencies": { "end-of-stream": "^1.1.0", "once": "^1.3.1" } }, "sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA=="], + "punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="], "queue-microtask": ["queue-microtask@1.2.3", "", {}, "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A=="], + "quick-lru": ["quick-lru@5.1.1", "", {}, "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA=="], + "react": ["react@19.2.4", "", {}, "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ=="], "react-dom": ["react-dom@19.2.4", "", { "dependencies": { "scheduler": "^0.27.0" }, "peerDependencies": { "react": "^19.2.4" } }, "sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ=="], - "react-i18next": ["react-i18next@17.0.1", "", { "dependencies": { "@babel/runtime": "^7.29.2", "html-parse-stringify": "^3.0.1", "use-sync-external-store": "^1.6.0" }, "peerDependencies": { "i18next": ">= 26.0.1", "react": ">= 16.8.0", "typescript": "^5 || ^6" }, "optionalPeers": ["typescript"] }, "sha512-iG65FGnFHcYyHNuT01ukffYWCOBFTWSdVD8EZd/dCVWgtjFPObcSsvYYNwcsokO/rDcTb5d6D8Acv8MrOdm6Hw=="], + "react-i18next": ["react-i18next@17.0.2", "", { "dependencies": { "@babel/runtime": "^7.29.2", "html-parse-stringify": "^3.0.1", "use-sync-external-store": "^1.6.0" }, "peerDependencies": { "i18next": ">= 26.0.1", "react": ">= 16.8.0", "typescript": "^5 || ^6" }, "optionalPeers": ["typescript"] }, "sha512-shBftH2vaTWK2Bsp7FiL+cevx3xFJlvFxmsDFQSrJc+6twHkP0tv/bGa01VVWzpreUVVwU+3Hev5iFqRg65RwA=="], "react-icons": ["react-icons@5.6.0", "", { "peerDependencies": { "react": "*" } }, "sha512-RH93p5ki6LfOiIt0UtDyNg/cee+HLVR6cHHtW3wALfo+eOHTp8RnU2kRkI6E+H19zMIs03DyxUG/GfZMOGvmiA=="], "react-is": ["react-is@16.13.1", "", {}, "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="], + "read-binary-file-arch": ["read-binary-file-arch@1.0.6", "", { "dependencies": { "debug": "^4.3.4" }, "bin": { "read-binary-file-arch": "cli.js" } }, "sha512-BNg9EN3DD3GsDXX7Aa8O4p92sryjkmzYYgmgTAc6CA4uGLEDzFfxOxugu21akOxpcXHiEgsYkC6nPsQvLLLmEg=="], + + "readable-stream": ["readable-stream@3.6.2", "", { "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", "util-deprecate": "^1.0.1" } }, "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA=="], + + "readdirp": ["readdirp@5.0.0", "", {}, "sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ=="], + "reflect.getprototypeof": ["reflect.getprototypeof@1.0.10", "", { "dependencies": { "call-bind": "^1.0.8", "define-properties": "^1.2.1", "es-abstract": "^1.23.9", "es-errors": "^1.3.0", "es-object-atoms": "^1.0.0", "get-intrinsic": "^1.2.7", "get-proto": "^1.0.1", "which-builtin-type": "^1.2.1" } }, "sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw=="], "regexp.prototype.flags": ["regexp.prototype.flags@1.5.4", "", { "dependencies": { "call-bind": "^1.0.8", "define-properties": "^1.2.1", "es-errors": "^1.3.0", "get-proto": "^1.0.1", "gopd": "^1.2.0", "set-function-name": "^2.0.2" } }, "sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA=="], + "require-directory": ["require-directory@2.1.1", "", {}, "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q=="], + "require-relative": ["require-relative@0.8.7", "", {}, "sha512-AKGr4qvHiryxRb19m3PsLRGuKVAbJLUD7E6eOaHkfKhwc+vSgVOCY5xNvm9EkolBKTOf0GrQAZKLimOCz81Khg=="], - "resolve": ["resolve@2.0.0-next.5", "", { "dependencies": { "is-core-module": "^2.13.0", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, "bin": { "resolve": "bin/resolve" } }, "sha512-U7WjGVG9sH8tvjW5SmGbQuui75FiyjAX72HX15DwBBwF9dNiQZRQAg9nnPhYy+TUnE0+VcrttuvNI8oSxZcocA=="], + "resedit": ["resedit@1.7.2", "", { "dependencies": { "pe-library": "^0.4.1" } }, "sha512-vHjcY2MlAITJhC0eRD/Vv8Vlgmu9Sd3LX9zZvtGzU5ZImdTN3+d6e/4mnTyV8vEbyf1sgNIrWxhWlrys52OkEA=="], + + "resolve": ["resolve@2.0.0-next.6", "", { "dependencies": { "es-errors": "^1.3.0", "is-core-module": "^2.16.1", "node-exports-info": "^1.6.0", "object-keys": "^1.1.1", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, "bin": { "resolve": "bin/resolve" } }, "sha512-3JmVl5hMGtJ3kMmB3zi3DL25KfkCEyy3Tw7Gmw7z5w8M9WlwoPFnIvwChzu1+cF3iaK3sp18hhPz8ANeimdJfA=="], + + "resolve-alpn": ["resolve-alpn@1.2.1", "", {}, "sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g=="], "resolve-from": ["resolve-from@4.0.0", "", {}, "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g=="], + "responselike": ["responselike@2.0.1", "", { "dependencies": { "lowercase-keys": "^2.0.0" } }, "sha512-4gl03wn3hj1HP3yzgdI7d3lCkF95F21Pz4BPGvKHinyQzALR5CapwC8yIi0Rh58DEMQ/SguC03wFj2k0M/mHhw=="], + + "restore-cursor": ["restore-cursor@3.1.0", "", { "dependencies": { "onetime": "^5.1.0", "signal-exit": "^3.0.2" } }, "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA=="], + + "retry": ["retry@0.12.0", "", {}, "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow=="], + "reusify": ["reusify@1.1.0", "", {}, "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw=="], "rimraf": ["rimraf@3.0.2", "", { "dependencies": { "glob": "^7.1.3" }, "bin": { "rimraf": "bin.js" } }, "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA=="], + "roarr": ["roarr@2.15.4", "", { "dependencies": { "boolean": "^3.0.1", "detect-node": "^2.0.4", "globalthis": "^1.0.1", "json-stringify-safe": "^5.0.1", "semver-compare": "^1.0.0", "sprintf-js": "^1.1.2" } }, "sha512-CHhPh+UNHD2GTXNYhPWLnU8ONHdI+5DI+4EYIAOaiD63rHeYlZvyh8P+in5999TTSFgUYuKUAjzRI4mdh/p+2A=="], + "robust-orientation": ["robust-orientation@1.2.1", "", { "dependencies": { "robust-scale": "^1.0.2", "robust-subtract": "^1.0.0", "robust-sum": "^1.0.0", "two-product": "^1.0.2" } }, "sha512-FuTptgKwY6iNuU15nrIJDLjXzCChWB+T4AvksRtwPS/WZ3HuP1CElCm1t+OBfgQKfWbtZIawip+61k7+buRKAg=="], "robust-point-in-polygon": ["robust-point-in-polygon@1.0.3", "", { "dependencies": { "robust-orientation": "^1.0.2" } }, "sha512-pPzz7AevOOcPYnFv4Vs5L0C7BKOq6C/TfAw5EUE58CylbjGiPyMjAnPLzzSuPZ2zftUGwWbmLWPOjPOz61tAcA=="], @@ -959,13 +1314,25 @@ "safe-array-concat": ["safe-array-concat@1.1.3", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.2", "get-intrinsic": "^1.2.6", "has-symbols": "^1.1.0", "isarray": "^2.0.5" } }, "sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q=="], + "safe-buffer": ["safe-buffer@5.2.1", "", {}, "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="], + "safe-push-apply": ["safe-push-apply@1.0.0", "", { "dependencies": { "es-errors": "^1.3.0", "isarray": "^2.0.5" } }, "sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA=="], "safe-regex-test": ["safe-regex-test@1.1.0", "", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "is-regex": "^1.2.1" } }, "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw=="], + "safer-buffer": ["safer-buffer@2.1.2", "", {}, "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="], + + "sanitize-filename": ["sanitize-filename@1.6.4", "", { "dependencies": { "truncate-utf8-bytes": "^1.0.0" } }, "sha512-9ZyI08PsvdQl2r/bBIGubpVdR3RR9sY6RDiWFPreA21C/EFlQhmgo20UZlNjZMMZNubusLhAQozkA0Od5J21Eg=="], + + "sax": ["sax@1.6.0", "", {}, "sha512-6R3J5M4AcbtLUdZmRv2SygeVaM7IhrLXu9BmnOGmmACak8fiUtOsYNWUS4uK7upbmHIBbLBeFeI//477BKLBzA=="], + "scheduler": ["scheduler@0.27.0", "", {}, "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q=="], - "semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], + "semver": ["semver@7.7.4", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA=="], + + "semver-compare": ["semver-compare@1.0.0", "", {}, "sha512-YM3/ITh2MJ5MtzaM429anh+x2jiLVjqILF4m4oyQB18W7Ggea7BfqdH/wGMK7dDiMghv/6WG7znWMwUDzJiXow=="], + + "serialize-error": ["serialize-error@7.0.1", "", { "dependencies": { "type-fest": "^0.13.1" } }, "sha512-8I8TjW5KMOKsZQTvoxjuSIa7foAwPWGOts+6o7sgjz41/qMD9VQHEDxi6PBvK2l0MXUmqZyNpUK+T2tQaaElvw=="], "set-function-length": ["set-function-length@1.2.2", "", { "dependencies": { "define-data-property": "^1.1.4", "es-errors": "^1.3.0", "function-bind": "^1.1.2", "get-intrinsic": "^1.2.4", "gopd": "^1.0.1", "has-property-descriptors": "^1.0.2" } }, "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg=="], @@ -985,18 +1352,44 @@ "side-channel-weakmap": ["side-channel-weakmap@1.0.2", "", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.5", "object-inspect": "^1.13.3", "side-channel-map": "^1.0.1" } }, "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A=="], + "signal-exit": ["signal-exit@3.0.7", "", {}, "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ=="], + + "simple-update-notifier": ["simple-update-notifier@2.0.0", "", { "dependencies": { "semver": "^7.5.3" } }, "sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w=="], + "skia-canvas": ["skia-canvas@3.0.8", "", { "dependencies": { "detect-libc": "^2.1.1", "follow-redirects": "^1.15.11", "https-proxy-agent": "^7.0.6", "string-split-by": "^1.0.0" } }, "sha512-FSYKxp8Ng2vOeeOBiyPhnn6ui6FirPJXMyjk4PKl8N/OWzVrkMawUgY9zubIWHMdYtyWFn0gfX3QlRwg6HBmdg=="], "slash": ["slash@3.0.0", "", {}, "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q=="], + "slice-ansi": ["slice-ansi@3.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "astral-regex": "^2.0.0", "is-fullwidth-code-point": "^3.0.0" } }, "sha512-pSyv7bSTC7ig9Dcgbw9AuRNUb5k5V6oDudjZoMBSr13qpLBG7tB+zgCkARjq7xIUgdz5P1Qe8u+rSGdouOOIyQ=="], + + "smart-buffer": ["smart-buffer@4.2.0", "", {}, "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg=="], + + "socks": ["socks@2.8.7", "", { "dependencies": { "ip-address": "^10.0.1", "smart-buffer": "^4.2.0" } }, "sha512-HLpt+uLy/pxB+bum/9DzAgiKS8CX1EvbWxI4zlmgGCExImLdiad2iCwXT5Z4c9c3Eq8rP2318mPW2c+QbtjK8A=="], + + "socks-proxy-agent": ["socks-proxy-agent@8.0.5", "", { "dependencies": { "agent-base": "^7.1.2", "debug": "^4.3.4", "socks": "^2.8.3" } }, "sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw=="], + + "source-map": ["source-map@0.6.1", "", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="], + "source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="], + "source-map-support": ["source-map-support@0.5.21", "", { "dependencies": { "buffer-from": "^1.0.0", "source-map": "^0.6.0" } }, "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w=="], + + "sprintf-js": ["sprintf-js@1.1.3", "", {}, "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA=="], + "ssim.js": ["ssim.js@3.5.0", "", {}, "sha512-Aj6Jl2z6oDmgYFFbQqK7fght19bXdOxY7Tj03nF+03M9gCBAjeIiO8/PlEGMfKDwYpw4q6iBqVq2YuREorGg/g=="], + "ssri": ["ssri@12.0.0", "", { "dependencies": { "minipass": "^7.0.3" } }, "sha512-S7iGNosepx9RadX82oimUkvr0Ct7IjJbEbs4mJcTxst8um95J3sDYU1RBEOvdu6oL1Wek2ODI5i4MAw+dZ6cAQ=="], + + "stat-mode": ["stat-mode@1.0.0", "", {}, "sha512-jH9EhtKIjuXZ2cWxmXS8ZP80XyC3iasQxMDV8jzhNJpfDb7VbQLVW4Wvsxz9QZvzV+G4YoSfBUVKDOyxLzi/sg=="], + "stop-iteration-iterator": ["stop-iteration-iterator@1.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "internal-slot": "^1.1.0" } }, "sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ=="], "string-split-by": ["string-split-by@1.0.0", "", { "dependencies": { "parenthesis": "^3.1.5" } }, "sha512-KaJKY+hfpzNyet/emP81PJA9hTVSfxNLS9SFTWxdCnnW1/zOOwiV248+EfoX7IQFcBaOp4G5YE6xTJMF+pLg6A=="], + "string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], + + "string-width-cjs": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], + "string.prototype.includes": ["string.prototype.includes@2.0.1", "", { "dependencies": { "call-bind": "^1.0.7", "define-properties": "^1.2.1", "es-abstract": "^1.23.3" } }, "sha512-o7+c9bW6zpAdJHTtujeePODAhkuicdAryFsfVKwA+wGw89wJ4GTY484WTucM9hLtDEOpOvI+aHnzqnC5lHp4Rg=="], "string.prototype.matchall": ["string.prototype.matchall@4.0.12", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.3", "define-properties": "^1.2.1", "es-abstract": "^1.23.6", "es-errors": "^1.3.0", "es-object-atoms": "^1.0.0", "get-intrinsic": "^1.2.6", "gopd": "^1.2.0", "has-symbols": "^1.1.0", "internal-slot": "^1.1.0", "regexp.prototype.flags": "^1.5.3", "set-function-name": "^2.0.2", "side-channel": "^1.1.0" } }, "sha512-6CC9uyBL+/48dYizRf7H7VAYCMCNTBeM78x/VTUe9bFEaxBepPJDa1Ow99LqI/1yF7kuy7Q3cQsYMrcjGUcskA=="], @@ -1009,33 +1402,55 @@ "string.prototype.trimstart": ["string.prototype.trimstart@1.0.8", "", { "dependencies": { "call-bind": "^1.0.7", "define-properties": "^1.2.1", "es-object-atoms": "^1.0.0" } }, "sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg=="], + "string_decoder": ["string_decoder@1.3.0", "", { "dependencies": { "safe-buffer": "~5.2.0" } }, "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA=="], + "strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], + "strip-ansi-cjs": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], + "strip-bom": ["strip-bom@3.0.0", "", {}, "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA=="], "strip-json-comments": ["strip-json-comments@3.1.1", "", {}, "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig=="], + "sumchecker": ["sumchecker@3.0.1", "", { "dependencies": { "debug": "^4.1.0" } }, "sha512-MvjXzkz/BOfyVDkG0oFOtBxHX2u3gKbMHIF/dXblZsgD3BWOFLmHovIpZY7BykJdAjcqRCBi1WYBNdEC9yI7vg=="], + "supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="], "supports-preserve-symlinks-flag": ["supports-preserve-symlinks-flag@1.0.0", "", {}, "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w=="], "synckit": ["synckit@0.11.12", "", { "dependencies": { "@pkgr/core": "^0.2.9" } }, "sha512-Bh7QjT8/SuKUIfObSXNHNSK6WHo6J1tHCqJsuaFDP7gP0fkzSfTxI8y85JrppZ0h8l0maIgc2tfuZQ6/t3GtnQ=="], - "tabbable": ["tabbable@6.2.0", "", {}, "sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew=="], + "tabbable": ["tabbable@6.4.0", "", {}, "sha512-05PUHKSNE8ou2dwIxTngl4EzcnsCDZGJ/iCLtDflR/SHB/ny14rXc+qU5P4mG9JkusiV7EivzY9Mhm55AzAvCg=="], "tailwindcss": ["tailwindcss@4.2.2", "", {}, "sha512-KWBIxs1Xb6NoLdMVqhbhgwZf2PGBpPEiwOqgI4pFIYbNTfBXiKYyWoTsXgBQ9WFg/OlhnvHaY+AEpW7wSmFo2Q=="], - "tapable": ["tapable@2.3.0", "", {}, "sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg=="], + "tapable": ["tapable@2.3.2", "", {}, "sha512-1MOpMXuhGzGL5TTCZFItxCc0AARf1EZFQkGqMm7ERKj8+Hgr5oLvJOVFcC+lRmR8hCe2S3jC4T5D7Vg/d7/fhA=="], + + "tar": ["tar@7.5.13", "", { "dependencies": { "@isaacs/fs-minipass": "^4.0.0", "chownr": "^3.0.0", "minipass": "^7.1.2", "minizlib": "^3.1.0", "yallist": "^5.0.0" } }, "sha512-tOG/7GyXpFevhXVh8jOPJrmtRpOTsYqUIkVdVooZYJS/z8WhfQUX8RJILmeuJNinGAMSu1veBr4asSHFt5/hng=="], + + "temp": ["temp@0.9.4", "", { "dependencies": { "mkdirp": "^0.5.1", "rimraf": "~2.6.2" } }, "sha512-yYrrsWnrXMcdsnu/7YMYAofM1ktpL5By7vZhf15CrXijWWrEYZks5AXBudalfSWJLlnen/QUJUB5aoB0kqZUGA=="], + + "temp-file": ["temp-file@3.4.0", "", { "dependencies": { "async-exit-hook": "^2.0.1", "fs-extra": "^10.0.0" } }, "sha512-C5tjlC/HCtVUOi3KWVokd4vHVViOmGjtLwIh4MuzPo/nMYTV/p1urt3RnMz2IWXDdKEGJH3k5+KPxtqRsUYGtg=="], "text-table": ["text-table@0.2.0", "", {}, "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw=="], "tiff": ["tiff@7.1.3", "", { "dependencies": { "fflate": "^0.8.2", "iobuffer": "^6.0.0" } }, "sha512-YEEq3fT++2pdta/9P/vGG4QRMdZQoe6W6JNaWnIi6NvAsbeNITwFCtmWwL/BZvOi+uo2I3ohyOkD3sZfme+c6g=="], + "tiny-async-pool": ["tiny-async-pool@1.3.0", "", { "dependencies": { "semver": "^5.5.0" } }, "sha512-01EAw5EDrcVrdgyCLgoSPvqznC0sVxDSVeiOz09FUpjh71G79VCqneOr+xvt7T1r76CF6ZZfPjHorN2+d+3mqA=="], + + "tiny-typed-emitter": ["tiny-typed-emitter@2.1.0", "", {}, "sha512-qVtvMxeXbVej0cQWKqVSSAHmKZEHAvxdF8HEUBFWts8h+xEo5m/lEiPakuyZ3BnCBjOD8i24kzNOiOLLgsSxhA=="], + "tinyglobby": ["tinyglobby@0.2.15", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.3" } }, "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ=="], + "tmp": ["tmp@0.2.5", "", {}, "sha512-voyz6MApa1rQGUxT3E+BK7/ROe8itEx7vD8/HEvt4xwXucvQ5G5oeEiHkmHZJuBO21RpOf+YYm9MOivj709jow=="], + + "tmp-promise": ["tmp-promise@3.0.3", "", { "dependencies": { "tmp": "^0.2.0" } }, "sha512-RwM7MoPojPxsOBYnyd2hy0bxtIlVrihNs9pj5SUvY8Zz1sQcQG2tG1hSr8PDxfgEB8RNKDhqbIlroIarSNDNsQ=="], + "to-regex-range": ["to-regex-range@5.0.1", "", { "dependencies": { "is-number": "^7.0.0" } }, "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ=="], - "ts-api-utils": ["ts-api-utils@2.4.0", "", { "peerDependencies": { "typescript": ">=4.8.4" } }, "sha512-3TaVTaAv2gTiMB35i3FiGJaRfwb3Pyn/j3m/bfAvGe8FB7CF6u+LMYqYlDh7reQf7UNvoTvdfAqHGmPGOSsPmA=="], + "truncate-utf8-bytes": ["truncate-utf8-bytes@1.0.2", "", { "dependencies": { "utf8-byte-length": "^1.0.1" } }, "sha512-95Pu1QXQvruGEhv62XCMO3Mm90GscOCClvrIUwCM0PYOXK3kaF3l3sIHxx71ThJfcbM2O5Au6SO3AWCSEfW4mQ=="], + + "ts-api-utils": ["ts-api-utils@2.5.0", "", { "peerDependencies": { "typescript": ">=4.8.4" } }, "sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA=="], "ts-pattern": ["ts-pattern@5.9.0", "", {}, "sha512-6s5V71mX8qBUmlgbrfL33xDUwO0fq48rxAu2LBE11WBeGdpCPOsXksQbZJHvHwhrd3QjUusd3mAOM5Gg0mFBLg=="], @@ -1061,17 +1476,31 @@ "typescript": ["typescript@6.0.2", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-bGdAIrZ0wiGDo5l8c++HWtbaNCWTS4UTv7RaTH/ThVIgjkveJt83m74bBHMJkuCbslY8ixgLBVZJIOiQlQTjfQ=="], - "typescript-eslint": ["typescript-eslint@8.57.2", "", { "dependencies": { "@typescript-eslint/eslint-plugin": "8.57.2", "@typescript-eslint/parser": "8.57.2", "@typescript-eslint/typescript-estree": "8.57.2", "@typescript-eslint/utils": "8.57.2" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-VEPQ0iPgWO/sBaZOU1xo4nuNdODVOajPnTIbog2GKYr31nIlZ0fWPoCQgGfF3ETyBl1vn63F/p50Um9Z4J8O8A=="], + "typescript-eslint": ["typescript-eslint@8.58.0", "", { "dependencies": { "@typescript-eslint/eslint-plugin": "8.58.0", "@typescript-eslint/parser": "8.58.0", "@typescript-eslint/typescript-estree": "8.58.0", "@typescript-eslint/utils": "8.58.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-e2TQzKfaI85fO+F3QywtX+tCTsu/D3WW5LVU6nz8hTFKFZ8yBJ6mSYRpXqdR3mFjPWmO0eWsTa5f+UpAOe/FMA=="], "uint8-base64": ["uint8-base64@1.0.0", "", {}, "sha512-B6ZJfEZL2FAhbIyZZ7WlQq8MPq1X1P1DUA//LsSv/vB4r+Dao5/BPoxgw2t+t1XyvoSfBkE7zI4SYsnGj7BmlQ=="], "unbox-primitive": ["unbox-primitive@1.1.0", "", { "dependencies": { "call-bound": "^1.0.3", "has-bigints": "^1.0.2", "has-symbols": "^1.1.0", "which-boxed-primitive": "^1.1.1" } }, "sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw=="], - "update-browserslist-db": ["update-browserslist-db@1.2.2", "", { "dependencies": { "escalade": "^3.2.0", "picocolors": "^1.1.1" }, "peerDependencies": { "browserslist": ">= 4.21.0" }, "bin": { "update-browserslist-db": "cli.js" } }, "sha512-E85pfNzMQ9jpKkA7+TJAi4TJN+tBCuWh5rUcS/sv6cFi+1q9LYDwDI5dpUL0u/73EElyQ8d3TEaeW4sPedBqYA=="], + "undici-types": ["undici-types@7.18.2", "", {}, "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w=="], + + "unique-filename": ["unique-filename@4.0.0", "", { "dependencies": { "unique-slug": "^5.0.0" } }, "sha512-XSnEewXmQ+veP7xX2dS5Q4yZAvO40cBN2MWkJ7D/6sW4Dg6wYBNwM1Vrnz1FhH5AdeLIlUXRI9e28z1YZi71NQ=="], + + "unique-slug": ["unique-slug@5.0.0", "", { "dependencies": { "imurmurhash": "^0.1.4" } }, "sha512-9OdaqO5kwqR+1kVgHAhsp5vPNU0hnxRa26rBFNfNgM7M6pNtgzeBn3s/xbyCQL3dcjzOatcef6UUHpB/6MaETg=="], + + "universalify": ["universalify@2.0.1", "", {}, "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw=="], + + "update-browserslist-db": ["update-browserslist-db@1.2.3", "", { "dependencies": { "escalade": "^3.2.0", "picocolors": "^1.1.1" }, "peerDependencies": { "browserslist": ">= 4.21.0" }, "bin": { "update-browserslist-db": "cli.js" } }, "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w=="], "uri-js": ["uri-js@4.4.1", "", { "dependencies": { "punycode": "^2.1.0" } }, "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg=="], - "use-sync-external-store": ["use-sync-external-store@1.5.0", "", { "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-Rb46I4cGGVBmjamjphe8L/UnvJD+uPPtTkNvX5mZgqdbavhI4EbgIWJiIHXJ8bc/i9EQGPRh4DwEURJ552Do0A=="], + "use-sync-external-store": ["use-sync-external-store@1.6.0", "", { "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w=="], + + "utf8-byte-length": ["utf8-byte-length@1.0.5", "", {}, "sha512-Xn0w3MtiQ6zoz2vFyUVruaCL53O/DwUvkEeOvj+uulMm0BkUGYWmBYVyElqZaSLhY6ZD0ulfU3aBra2aVT4xfA=="], + + "util-deprecate": ["util-deprecate@1.0.2", "", {}, "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="], + + "verror": ["verror@1.10.1", "", { "dependencies": { "assert-plus": "^1.0.0", "core-util-is": "1.0.2", "extsprintf": "^1.2.0" } }, "sha512-veufcmxri4e3XSrT0xwfUR7kguIkaxBeosDg00yDWhk49wdwkSUrvvsm7nc75e1PUyvIeZj6nS8VQRYz2/S4Xg=="], "vite": ["vite@8.0.3", "", { "dependencies": { "lightningcss": "^1.32.0", "picomatch": "^4.0.4", "postcss": "^8.5.8", "rolldown": "1.0.0-rc.12", "tinyglobby": "^0.2.15" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "@vitejs/devtools": "^0.1.0", "esbuild": "^0.27.0", "jiti": ">=1.21.0", "less": "^4.0.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "@vitejs/devtools", "esbuild", "jiti", "less", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-B9ifbFudT1TFhfltfaIPgjo9Z3mDynBTJSUYxTjOQruf/zHH+ezCQKcoqO+h7a9Pw9Nm/OtlXAiGT1axBgwqrQ=="], @@ -1079,7 +1508,9 @@ "vue-eslint-parser": ["vue-eslint-parser@9.4.3", "", { "dependencies": { "debug": "^4.3.4", "eslint-scope": "^7.1.1", "eslint-visitor-keys": "^3.3.0", "espree": "^9.3.1", "esquery": "^1.4.0", "lodash": "^4.17.21", "semver": "^7.3.6" }, "peerDependencies": { "eslint": ">=6.0.0" } }, "sha512-2rYRLWlIpaiN8xbPiDyXZXRgLGOtWxERV7ND5fFAv5qo1D2N9Fu9MNajBNc6o13lZ+24DAWCkQCvj4klgmcITg=="], - "which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="], + "wcwidth": ["wcwidth@1.0.1", "", { "dependencies": { "defaults": "^1.0.3" } }, "sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg=="], + + "which": ["which@5.0.0", "", { "dependencies": { "isexe": "^3.1.1" }, "bin": { "node-which": "bin/which.js" } }, "sha512-JEdGzHwwkrbWoGOlIHqQ5gtprKGOenpDHpxE9zVR1bWbOtYRyPPHMe9FaP6x61CmNaTThSkb0DAJte5jD+DmzQ=="], "which-boxed-primitive": ["which-boxed-primitive@1.1.1", "", { "dependencies": { "is-bigint": "^1.1.0", "is-boolean-object": "^1.2.1", "is-number-object": "^1.1.1", "is-string": "^1.1.1", "is-symbol": "^1.1.1" } }, "sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA=="], @@ -1091,159 +1522,211 @@ "word-wrap": ["word-wrap@1.2.5", "", {}, "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA=="], + "wrap-ansi": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="], + + "wrap-ansi-cjs": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="], + "wrappy": ["wrappy@1.0.2", "", {}, "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="], - "yallist": ["yallist@3.1.1", "", {}, "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="], + "xmlbuilder": ["xmlbuilder@15.1.1", "", {}, "sha512-yMqGBqtXyeN1e3TGYvgNgDVZ3j84W4cwkOXQswghol6APgZWaff9lnbvN7MHYJOiXsvGPXtjTYJEiC9J2wv9Eg=="], + + "y18n": ["y18n@5.0.8", "", {}, "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA=="], + + "yallist": ["yallist@5.0.0", "", {}, "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw=="], + + "yargs": ["yargs@17.7.2", "", { "dependencies": { "cliui": "^8.0.1", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", "require-directory": "^2.1.1", "string-width": "^4.2.3", "y18n": "^5.0.5", "yargs-parser": "^21.1.1" } }, "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w=="], + + "yargs-parser": ["yargs-parser@21.1.1", "", {}, "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw=="], + + "yauzl": ["yauzl@2.10.0", "", { "dependencies": { "buffer-crc32": "~0.2.3", "fd-slicer": "~1.1.0" } }, "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g=="], "yocto-queue": ["yocto-queue@0.1.0", "", {}, "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q=="], - "zod": ["zod@3.25.28", "", {}, "sha512-/nt/67WYKnr5by3YS7LroZJbtcCBurDKKPBPWWzaxvVCGuG/NOsiKkrjoOhI8mJ+SQUXEbUzeB3S+6XDUEEj7Q=="], + "zod": ["zod@4.3.6", "", {}, "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg=="], "zod-validation-error": ["zod-validation-error@4.0.2", "", { "peerDependencies": { "zod": "^3.25.0 || ^4.0.0" } }, "sha512-Q6/nZLe6jxuU80qb/4uJ4t5v2VEZ44lzQjPDhYJNztRQ4wyWc6VF3D3Kb/fAuPetZQnhS3hnajCf9CsWesghLQ=="], - "@babel/core/debug": ["debug@4.4.0", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA=="], + "@babel/core/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], - "@babel/core/json5": ["json5@2.2.3", "", { "bin": { "json5": "lib/cli.js" } }, "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg=="], + "@babel/helper-compilation-targets/lru-cache": ["lru-cache@5.1.1", "", { "dependencies": { "yallist": "^3.0.2" } }, "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w=="], - "@babel/generator/@babel/parser": ["@babel/parser@7.28.3", "", { "dependencies": { "@babel/types": "^7.28.2" }, "bin": "./bin/babel-parser.js" }, "sha512-7+Ey1mAgYqFAx2h0RuoxcQT5+MlG3GTV0TQrgr7/ZliKsm/MNDxVVutlWaziMq7wJNAz8MTqz55XLpWvva6StA=="], + "@babel/helper-compilation-targets/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], - "@babel/generator/@babel/types": ["@babel/types@7.28.2", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.27.1" } }, "sha512-ruv7Ae4J5dUYULmeXw1gmb7rYRz57OWCPM57pHojnLq/3Z1CK2lNSLTCVjxVk1F/TZHwOZZrOWi0ur95BbLxNQ=="], + "@electron/asar/commander": ["commander@5.1.0", "", {}, "sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg=="], - "@babel/helper-compilation-targets/browserslist": ["browserslist@4.24.4", "", { "dependencies": { "caniuse-lite": "^1.0.30001688", "electron-to-chromium": "^1.5.73", "node-releases": "^2.0.19", "update-browserslist-db": "^1.1.1" }, "bin": { "browserslist": "cli.js" } }, "sha512-KDi1Ny1gSePi1vm0q4oxSF8b4DR44GF4BbmS2YdhPLOEqd8pDviZOGH/GsmRwoWJ2+5Lr085X7naowMwKHDG1A=="], + "@electron/asar/glob": ["glob@7.2.3", "", { "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", "minimatch": "^3.1.1", "once": "^1.3.0", "path-is-absolute": "^1.0.0" } }, "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q=="], - "@babel/helper-compilation-targets/lru-cache": ["lru-cache@5.1.1", "", { "dependencies": { "yallist": "^3.0.2" } }, "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w=="], + "@electron/asar/minimatch": ["minimatch@3.1.5", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w=="], - "@babel/helper-module-imports/@babel/traverse": ["@babel/traverse@7.28.0", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.0", "@babel/helper-globals": "^7.28.0", "@babel/parser": "^7.28.0", "@babel/template": "^7.27.2", "@babel/types": "^7.28.0", "debug": "^4.3.1" } }, "sha512-mGe7UK5wWyh0bKRfupsUchrQGqvDbZDbKJw+kcRGSmdHVYrv+ltd0pnpDTVpiTqnaBru9iEvA8pz8W46v0Amwg=="], + "@electron/fuses/fs-extra": ["fs-extra@9.1.0", "", { "dependencies": { "at-least-node": "^1.0.0", "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", "universalify": "^2.0.0" } }, "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ=="], - "@babel/helper-module-imports/@babel/types": ["@babel/types@7.28.0", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.27.1" } }, "sha512-jYnje+JyZG5YThjHiF28oT4SIZLnYOcSBb6+SDaFIyzDVSkXQmQQYclJ2R+YxcdmK0AX6x1E5OQNtuh3jHDrUg=="], + "@electron/get/fs-extra": ["fs-extra@8.1.0", "", { "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^4.0.0", "universalify": "^0.1.0" } }, "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g=="], - "@babel/helper-module-transforms/@babel/traverse": ["@babel/traverse@7.28.3", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.3", "@babel/helper-globals": "^7.28.0", "@babel/parser": "^7.28.3", "@babel/template": "^7.27.2", "@babel/types": "^7.28.2", "debug": "^4.3.1" } }, "sha512-7w4kZYHneL3A6NP2nxzHvT3HCZ7puDZZjFMqDpBPECub79sTtSO5CGXDkKrTQq8ksAwfD/XI2MRFX23njdDaIQ=="], + "@electron/get/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], - "@babel/template/@babel/parser": ["@babel/parser@7.27.2", "", { "dependencies": { "@babel/types": "^7.27.1" }, "bin": "./bin/babel-parser.js" }, "sha512-QYLs8299NA7WM/bZAdp+CviYYkVoYXlDW2rzliy3chxd1PQjej7JORuMJDJXJUb9g0TT+B99EwaVLKmX+sPXWw=="], + "@electron/notarize/fs-extra": ["fs-extra@9.1.0", "", { "dependencies": { "at-least-node": "^1.0.0", "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", "universalify": "^2.0.0" } }, "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ=="], - "@babel/template/@babel/types": ["@babel/types@7.27.1", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.27.1" } }, "sha512-+EzkxvLNfiUeKMgy/3luqfsCWFRXLb7U6wNQTk60tovuckwB15B191tJWvpp4HjiQWdJkCxO3Wbvc6jlk3Xb2Q=="], + "@electron/osx-sign/isbinaryfile": ["isbinaryfile@4.0.10", "", {}, "sha512-iHrqe5shvBUcFbmZq9zOQHBoeOhZJu6RQGrDpBgenUm/Am+F3JM2MgQj+rK3Z601fzrL5gLZWtAPH2OBaSVcyw=="], - "@babel/traverse/debug": ["debug@4.4.0", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA=="], + "@electron/universal/fs-extra": ["fs-extra@11.3.4", "", { "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", "universalify": "^2.0.0" } }, "sha512-CTXd6rk/M3/ULNQj8FBqBWHYBVYybQ3VPBw0xGKFe3tuH7ytT6ACnvzpIQ3UZtB8yvUKC2cXn1a+x+5EVQLovA=="], - "@eslint-community/eslint-utils/eslint-visitor-keys": ["eslint-visitor-keys@3.4.3", "", {}, "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag=="], + "@electron/universal/minimatch": ["minimatch@9.0.9", "", { "dependencies": { "brace-expansion": "^2.0.2" } }, "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg=="], - "@eslint/eslintrc/ajv": ["ajv@6.12.6", "", { "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", "json-schema-traverse": "^0.4.1", "uri-js": "^4.2.2" } }, "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g=="], + "@electron/windows-sign/fs-extra": ["fs-extra@11.3.4", "", { "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", "universalify": "^2.0.0" } }, "sha512-CTXd6rk/M3/ULNQj8FBqBWHYBVYybQ3VPBw0xGKFe3tuH7ytT6ACnvzpIQ3UZtB8yvUKC2cXn1a+x+5EVQLovA=="], + + "@eslint-community/eslint-utils/eslint-visitor-keys": ["eslint-visitor-keys@3.4.3", "", {}, "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag=="], "@eslint/eslintrc/espree": ["espree@9.6.1", "", { "dependencies": { "acorn": "^8.9.0", "acorn-jsx": "^5.3.2", "eslint-visitor-keys": "^3.4.1" } }, "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ=="], "@eslint/eslintrc/globals": ["globals@13.24.0", "", { "dependencies": { "type-fest": "^0.20.2" } }, "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ=="], - "@eslint/eslintrc/minimatch": ["minimatch@3.1.2", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw=="], + "@eslint/eslintrc/minimatch": ["minimatch@3.1.5", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w=="], + + "@humanwhocodes/config-array/minimatch": ["minimatch@3.1.5", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w=="], - "@fabianlars/tauri-plugin-oauth/@tauri-apps/api": ["@tauri-apps/api@2.3.0", "", {}, "sha512-33Z+0lX2wgZbx1SPFfqvzI6su63hCBkbzv+5NexeYjIx7WA9htdOKoRR7Dh3dJyltqS5/J8vQFyybiRoaL0hlA=="], + "@isaacs/cliui/string-width": ["string-width@5.1.2", "", { "dependencies": { "eastasianwidth": "^0.2.0", "emoji-regex": "^9.2.2", "strip-ansi": "^7.0.1" } }, "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA=="], - "@humanfs/node/@humanwhocodes/retry": ["@humanwhocodes/retry@0.3.1", "", {}, "sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA=="], + "@isaacs/cliui/strip-ansi": ["strip-ansi@7.2.0", "", { "dependencies": { "ansi-regex": "^6.2.2" } }, "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w=="], - "@humanwhocodes/config-array/minimatch": ["minimatch@3.1.2", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw=="], + "@isaacs/cliui/wrap-ansi": ["wrap-ansi@8.1.0", "", { "dependencies": { "ansi-styles": "^6.1.0", "string-width": "^5.0.1", "strip-ansi": "^7.0.1" } }, "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ=="], - "@jridgewell/gen-mapping/@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.0", "", {}, "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ=="], + "@malept/flatpak-bundler/fs-extra": ["fs-extra@9.1.0", "", { "dependencies": { "at-least-node": "^1.0.0", "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", "universalify": "^2.0.0" } }, "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ=="], - "@jridgewell/trace-mapping/@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.0", "", {}, "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ=="], + "@npmcli/agent/lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="], - "@tailwindcss/oxide-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.8.1", "", { "dependencies": { "@emnapi/wasi-threads": "1.1.0", "tslib": "^2.4.0" }, "bundled": true }, "sha512-AvT9QFpxK0Zd8J0jopedNm+w/2fIzvtPKPjqyw9jwvBaReTTqPBk9Hixaz7KbjimP+QNz605/XnjFcDAL2pqBg=="], + "@tailwindcss/oxide-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.9.2", "", { "dependencies": { "@emnapi/wasi-threads": "1.2.1", "tslib": "^2.4.0" }, "bundled": true }, "sha512-UC+ZhH3XtczQYfOlu3lNEkdW/p4dsJ1r/bP7H8+rhao3TTTMO1ATq/4DdIi23XuGoFY+Cz0JmCbdVl0hz9jZcA=="], - "@tailwindcss/oxide-wasm32-wasi/@emnapi/runtime": ["@emnapi/runtime@1.8.1", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-mehfKSMWjjNol8659Z8KxEMrdSJDDot5SXMq00dM8BN4o+CLNXQ0xH2V7EchNHV4RmbZLmmPdEaXZc5H2FXmDg=="], + "@tailwindcss/oxide-wasm32-wasi/@emnapi/runtime": ["@emnapi/runtime@1.9.2", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-3U4+MIWHImeyu1wnmVygh5WlgfYDtyf0k8AbLhMFxOipihf6nrWC4syIm/SwEeec0mNSafiiNnMJwbza/Is6Lw=="], - "@tailwindcss/oxide-wasm32-wasi/@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.1.0", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ=="], + "@tailwindcss/oxide-wasm32-wasi/@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.2.1", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w=="], - "@tailwindcss/oxide-wasm32-wasi/@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@1.1.1", "", { "dependencies": { "@emnapi/core": "^1.7.1", "@emnapi/runtime": "^1.7.1", "@tybys/wasm-util": "^0.10.1" }, "bundled": true }, "sha512-p64ah1M1ld8xjWv3qbvFwHiFVWrq1yFvV4f7w+mzaqiR4IlSgkqhcRdHwsGgomwzBH51sRY4NEowLxnaBjcW/A=="], + "@tailwindcss/oxide-wasm32-wasi/@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@1.1.2", "", { "dependencies": { "@tybys/wasm-util": "^0.10.1" }, "peerDependencies": { "@emnapi/core": "^1.7.1", "@emnapi/runtime": "^1.7.1" }, "bundled": true }, "sha512-sNXv5oLJ7ob93xkZ1XnxisYhGYXfaG9f65/ZgYuAu3qt7b3NadcOEhLvx28hv31PgX8SZJRYrAIPQilQmFpLVw=="], "@tailwindcss/oxide-wasm32-wasi/@tybys/wasm-util": ["@tybys/wasm-util@0.10.1", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg=="], "@tailwindcss/oxide-wasm32-wasi/tslib": ["tslib@2.8.1", "", { "bundled": true }, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], - "@tauri-apps/plugin-autostart/@tauri-apps/api": ["@tauri-apps/api@2.9.0", "", {}, "sha512-qD5tMjh7utwBk9/5PrTA/aGr3i5QaJ/Mlt7p8NilQ45WgbifUNPyKWsA63iQ8YfQq6R8ajMapU+/Q8nMcPRLNw=="], + "@types/cacheable-request/@types/node": ["@types/node@24.12.2", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-A1sre26ke7HDIuY/M23nd9gfB+nrmhtYyMINbjI1zHJxYteKR6qSMX56FsmjMcDb3SMcjJg5BiRRgOCC/yBD0g=="], - "@tauri-apps/plugin-cli/@tauri-apps/api": ["@tauri-apps/api@2.9.0", "", {}, "sha512-qD5tMjh7utwBk9/5PrTA/aGr3i5QaJ/Mlt7p8NilQ45WgbifUNPyKWsA63iQ8YfQq6R8ajMapU+/Q8nMcPRLNw=="], + "@types/fs-extra/@types/node": ["@types/node@24.12.2", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-A1sre26ke7HDIuY/M23nd9gfB+nrmhtYyMINbjI1zHJxYteKR6qSMX56FsmjMcDb3SMcjJg5BiRRgOCC/yBD0g=="], - "@tauri-apps/plugin-clipboard-manager/@tauri-apps/api": ["@tauri-apps/api@2.9.0", "", {}, "sha512-qD5tMjh7utwBk9/5PrTA/aGr3i5QaJ/Mlt7p8NilQ45WgbifUNPyKWsA63iQ8YfQq6R8ajMapU+/Q8nMcPRLNw=="], + "@types/keyv/@types/node": ["@types/node@24.12.2", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-A1sre26ke7HDIuY/M23nd9gfB+nrmhtYyMINbjI1zHJxYteKR6qSMX56FsmjMcDb3SMcjJg5BiRRgOCC/yBD0g=="], - "@tauri-apps/plugin-dialog/@tauri-apps/api": ["@tauri-apps/api@2.9.1", "", {}, "sha512-IGlhP6EivjXHepbBic618GOmiWe4URJiIeZFlB7x3czM0yDHHYviH1Xvoiv4FefdkQtn6v7TuwWCRfOGdnVUGw=="], + "@types/plist/@types/node": ["@types/node@24.12.2", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-A1sre26ke7HDIuY/M23nd9gfB+nrmhtYyMINbjI1zHJxYteKR6qSMX56FsmjMcDb3SMcjJg5BiRRgOCC/yBD0g=="], - "@tauri-apps/plugin-fs/@tauri-apps/api": ["@tauri-apps/api@2.9.1", "", {}, "sha512-IGlhP6EivjXHepbBic618GOmiWe4URJiIeZFlB7x3czM0yDHHYviH1Xvoiv4FefdkQtn6v7TuwWCRfOGdnVUGw=="], + "@types/responselike/@types/node": ["@types/node@24.12.2", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-A1sre26ke7HDIuY/M23nd9gfB+nrmhtYyMINbjI1zHJxYteKR6qSMX56FsmjMcDb3SMcjJg5BiRRgOCC/yBD0g=="], - "@tauri-apps/plugin-notification/@tauri-apps/api": ["@tauri-apps/api@2.9.0", "", {}, "sha512-qD5tMjh7utwBk9/5PrTA/aGr3i5QaJ/Mlt7p8NilQ45WgbifUNPyKWsA63iQ8YfQq6R8ajMapU+/Q8nMcPRLNw=="], + "@types/yauzl/@types/node": ["@types/node@24.12.2", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-A1sre26ke7HDIuY/M23nd9gfB+nrmhtYyMINbjI1zHJxYteKR6qSMX56FsmjMcDb3SMcjJg5BiRRgOCC/yBD0g=="], - "@tauri-apps/plugin-opener/@tauri-apps/api": ["@tauri-apps/api@2.9.1", "", {}, "sha512-IGlhP6EivjXHepbBic618GOmiWe4URJiIeZFlB7x3czM0yDHHYviH1Xvoiv4FefdkQtn6v7TuwWCRfOGdnVUGw=="], + "@typescript-eslint/eslint-plugin/@typescript-eslint/parser": ["@typescript-eslint/parser@8.58.0", "", { "dependencies": { "@typescript-eslint/scope-manager": "8.58.0", "@typescript-eslint/types": "8.58.0", "@typescript-eslint/typescript-estree": "8.58.0", "@typescript-eslint/visitor-keys": "8.58.0", "debug": "^4.4.3" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-rLoGZIf9afaRBYsPUMtvkDWykwXwUPL60HebR4JgTI8mxfFe2cQTu3AGitANp4b9B2QlVru6WzjgB2IzJKiCSA=="], - "@tauri-apps/plugin-os/@tauri-apps/api": ["@tauri-apps/api@2.9.0", "", {}, "sha512-qD5tMjh7utwBk9/5PrTA/aGr3i5QaJ/Mlt7p8NilQ45WgbifUNPyKWsA63iQ8YfQq6R8ajMapU+/Q8nMcPRLNw=="], + "@typescript-eslint/eslint-plugin/@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.58.0", "", { "dependencies": { "@typescript-eslint/types": "8.58.0", "@typescript-eslint/visitor-keys": "8.58.0" } }, "sha512-W1Lur1oF50FxSnNdGp3Vs6P+yBRSmZiw4IIjEeYxd8UQJwhUF0gDgDD/W/Tgmh73mxgEU3qX0Bzdl/NGuSPEpQ=="], - "@tauri-apps/plugin-store/@tauri-apps/api": ["@tauri-apps/api@2.9.1", "", {}, "sha512-IGlhP6EivjXHepbBic618GOmiWe4URJiIeZFlB7x3czM0yDHHYviH1Xvoiv4FefdkQtn6v7TuwWCRfOGdnVUGw=="], + "@typescript-eslint/eslint-plugin/@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.58.0", "", { "dependencies": { "@typescript-eslint/types": "8.58.0", "eslint-visitor-keys": "^5.0.0" } }, "sha512-XJ9UD9+bbDo4a4epraTwG3TsNPeiB9aShrUneAVXy8q4LuwowN+qu89/6ByLMINqvIMeI9H9hOHQtg/ijrYXzQ=="], - "@typescript-eslint/eslint-plugin/@typescript-eslint/parser": ["@typescript-eslint/parser@8.57.2", "", { "dependencies": { "@typescript-eslint/scope-manager": "8.57.2", "@typescript-eslint/types": "8.57.2", "@typescript-eslint/typescript-estree": "8.57.2", "@typescript-eslint/visitor-keys": "8.57.2", "debug": "^4.4.3" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-30ScMRHIAD33JJQkgfGW1t8CURZtjc2JpTrq5n2HFhOefbAhb7ucc7xJwdWcrEtqUIYJ73Nybpsggii6GtAHjA=="], + "@typescript-eslint/eslint-plugin/ignore": ["ignore@7.0.5", "", {}, "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg=="], - "@typescript-eslint/eslint-plugin/@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.57.2", "", { "dependencies": { "@typescript-eslint/types": "8.57.2", "@typescript-eslint/visitor-keys": "8.57.2" } }, "sha512-snZKH+W4WbWkrBqj4gUNRIGb/jipDW3qMqVJ4C9rzdFc+wLwruxk+2a5D+uoFcKPAqyqEnSb4l2ULuZf95eSkw=="], + "@typescript-eslint/parser/@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@6.21.0", "", { "dependencies": { "@typescript-eslint/types": "6.21.0", "@typescript-eslint/visitor-keys": "6.21.0", "debug": "^4.3.4", "globby": "^11.1.0", "is-glob": "^4.0.3", "minimatch": "9.0.3", "semver": "^7.5.4", "ts-api-utils": "^1.0.1" } }, "sha512-6npJTkZcO+y2/kr+z0hc4HwNfrrP4kNYh57ek7yCNlrBjWQ1Y0OS7jiZTkgumrvkX5HkEKXFZkkdFNkaW2wmUQ=="], - "@typescript-eslint/eslint-plugin/@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.57.2", "", { "dependencies": { "@typescript-eslint/types": "8.57.2", "eslint-visitor-keys": "^5.0.0" } }, "sha512-zhahknjobV2FiD6Ee9iLbS7OV9zi10rG26odsQdfBO/hjSzUQbkIYgda+iNKK1zNiW2ey+Lf8MU5btN17V3dUw=="], + "@typescript-eslint/project-service/@typescript-eslint/types": ["@typescript-eslint/types@8.58.0", "", {}, "sha512-O9CjxypDT89fbHxRfETNoAnHj/i6IpRK0CvbVN3qibxlLdo5p5hcLmUuCCrHMpxiWSwKyI8mCP7qRNYuOJ0Uww=="], - "@typescript-eslint/eslint-plugin/ignore": ["ignore@7.0.5", "", {}, "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg=="], + "@typescript-eslint/type-utils/@typescript-eslint/types": ["@typescript-eslint/types@8.58.0", "", {}, "sha512-O9CjxypDT89fbHxRfETNoAnHj/i6IpRK0CvbVN3qibxlLdo5p5hcLmUuCCrHMpxiWSwKyI8mCP7qRNYuOJ0Uww=="], - "@typescript-eslint/parser/@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@6.21.0", "", { "dependencies": { "@typescript-eslint/types": "6.21.0", "@typescript-eslint/visitor-keys": "6.21.0", "debug": "^4.3.4", "globby": "^11.1.0", "is-glob": "^4.0.3", "minimatch": "9.0.3", "semver": "^7.5.4", "ts-api-utils": "^1.0.1" } }, "sha512-6npJTkZcO+y2/kr+z0hc4HwNfrrP4kNYh57ek7yCNlrBjWQ1Y0OS7jiZTkgumrvkX5HkEKXFZkkdFNkaW2wmUQ=="], + "@typescript-eslint/typescript-estree/@typescript-eslint/types": ["@typescript-eslint/types@8.58.0", "", {}, "sha512-O9CjxypDT89fbHxRfETNoAnHj/i6IpRK0CvbVN3qibxlLdo5p5hcLmUuCCrHMpxiWSwKyI8mCP7qRNYuOJ0Uww=="], - "@typescript-eslint/project-service/@typescript-eslint/types": ["@typescript-eslint/types@8.57.2", "", {}, "sha512-/iZM6FnM4tnx9csuTxspMW4BOSegshwX5oBDznJ7S4WggL7Vczz5d2W11ecc4vRrQMQHXRSxzrCsyG5EsPPTbA=="], + "@typescript-eslint/typescript-estree/@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.58.0", "", { "dependencies": { "@typescript-eslint/types": "8.58.0", "eslint-visitor-keys": "^5.0.0" } }, "sha512-XJ9UD9+bbDo4a4epraTwG3TsNPeiB9aShrUneAVXy8q4LuwowN+qu89/6ByLMINqvIMeI9H9hOHQtg/ijrYXzQ=="], - "@typescript-eslint/type-utils/@typescript-eslint/types": ["@typescript-eslint/types@8.57.2", "", {}, "sha512-/iZM6FnM4tnx9csuTxspMW4BOSegshwX5oBDznJ7S4WggL7Vczz5d2W11ecc4vRrQMQHXRSxzrCsyG5EsPPTbA=="], + "@typescript-eslint/utils/@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.58.0", "", { "dependencies": { "@typescript-eslint/types": "8.58.0", "@typescript-eslint/visitor-keys": "8.58.0" } }, "sha512-W1Lur1oF50FxSnNdGp3Vs6P+yBRSmZiw4IIjEeYxd8UQJwhUF0gDgDD/W/Tgmh73mxgEU3qX0Bzdl/NGuSPEpQ=="], - "@typescript-eslint/typescript-estree/@typescript-eslint/types": ["@typescript-eslint/types@8.57.2", "", {}, "sha512-/iZM6FnM4tnx9csuTxspMW4BOSegshwX5oBDznJ7S4WggL7Vczz5d2W11ecc4vRrQMQHXRSxzrCsyG5EsPPTbA=="], + "@typescript-eslint/utils/@typescript-eslint/types": ["@typescript-eslint/types@8.58.0", "", {}, "sha512-O9CjxypDT89fbHxRfETNoAnHj/i6IpRK0CvbVN3qibxlLdo5p5hcLmUuCCrHMpxiWSwKyI8mCP7qRNYuOJ0Uww=="], - "@typescript-eslint/typescript-estree/@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.57.2", "", { "dependencies": { "@typescript-eslint/types": "8.57.2", "eslint-visitor-keys": "^5.0.0" } }, "sha512-zhahknjobV2FiD6Ee9iLbS7OV9zi10rG26odsQdfBO/hjSzUQbkIYgda+iNKK1zNiW2ey+Lf8MU5btN17V3dUw=="], + "@typescript-eslint/visitor-keys/eslint-visitor-keys": ["eslint-visitor-keys@3.4.3", "", {}, "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag=="], - "@typescript-eslint/typescript-estree/semver": ["semver@7.7.3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q=="], + "app-builder-lib/@electron/get": ["@electron/get@3.1.0", "", { "dependencies": { "debug": "^4.1.1", "env-paths": "^2.2.0", "fs-extra": "^8.1.0", "got": "^11.8.5", "progress": "^2.0.3", "semver": "^6.2.0", "sumchecker": "^3.0.1" }, "optionalDependencies": { "global-agent": "^3.0.0" } }, "sha512-F+nKc0xW+kVbBRhFzaMgPy3KwmuNTYX1fx6+FxxoSnNgwYX6LD7AKBTWkU0MQ6IBoe7dz069CNkR673sPAgkCQ=="], - "@typescript-eslint/utils/@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.57.2", "", { "dependencies": { "@typescript-eslint/types": "8.57.2", "@typescript-eslint/visitor-keys": "8.57.2" } }, "sha512-snZKH+W4WbWkrBqj4gUNRIGb/jipDW3qMqVJ4C9rzdFc+wLwruxk+2a5D+uoFcKPAqyqEnSb4l2ULuZf95eSkw=="], + "app-builder-lib/ci-info": ["ci-info@4.3.1", "", {}, "sha512-Wdy2Igu8OcBpI2pZePZ5oWjPC38tmDVx5WKUXKwlLYkA0ozo85sLsLvkBbBn/sZaSCMFOGZJ14fvW9t5/d7kdA=="], - "@typescript-eslint/utils/@typescript-eslint/types": ["@typescript-eslint/types@8.57.2", "", {}, "sha512-/iZM6FnM4tnx9csuTxspMW4BOSegshwX5oBDznJ7S4WggL7Vczz5d2W11ecc4vRrQMQHXRSxzrCsyG5EsPPTbA=="], + "cacache/glob": ["glob@10.5.0", "", { "dependencies": { "foreground-child": "^3.1.0", "jackspeak": "^3.1.2", "minimatch": "^9.0.4", "minipass": "^7.1.2", "package-json-from-dist": "^1.0.0", "path-scurry": "^1.11.1" }, "bin": { "glob": "dist/esm/bin.mjs" } }, "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg=="], - "@typescript-eslint/visitor-keys/eslint-visitor-keys": ["eslint-visitor-keys@3.4.3", "", {}, "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag=="], + "cacache/lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="], - "browserslist/caniuse-lite": ["caniuse-lite@1.0.30001760", "", {}, "sha512-7AAMPcueWELt1p3mi13HR/LHH0TJLT11cnwDJEs3xA4+CK/PLKeO9Kl1oru24htkyUKtkGCvAx4ohB0Ttry8Dw=="], + "clone-response/mimic-response": ["mimic-response@1.0.1", "", {}, "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ=="], - "chalk/ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="], + "cross-spawn/which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="], - "eslint-import-resolver-node/debug": ["debug@3.2.7", "", { "dependencies": { "ms": "^2.1.1" } }, "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ=="], + "dir-compare/minimatch": ["minimatch@3.1.5", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w=="], + + "electron/@types/node": ["@types/node@24.12.2", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-A1sre26ke7HDIuY/M23nd9gfB+nrmhtYyMINbjI1zHJxYteKR6qSMX56FsmjMcDb3SMcjJg5BiRRgOCC/yBD0g=="], + + "electron-winstaller/fs-extra": ["fs-extra@7.0.1", "", { "dependencies": { "graceful-fs": "^4.1.2", "jsonfile": "^4.0.0", "universalify": "^0.1.0" } }, "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw=="], - "eslint-import-resolver-node/resolve": ["resolve@1.22.11", "", { "dependencies": { "is-core-module": "^2.16.1", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, "bin": { "resolve": "bin/resolve" } }, "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ=="], + "eslint/@eslint/plugin-kit": ["@eslint/plugin-kit@0.7.0", "", { "dependencies": { "@eslint/core": "^1.2.0", "levn": "^0.4.1" } }, "sha512-ejvBr8MQCbVsWNZnCwDXjUKq40MDmHalq7cJ6e9s/qzTUFIIo/afzt1Vui9T97FM/V/pN4YsFVoed5NIa96RDg=="], + + "eslint-import-resolver-node/debug": ["debug@3.2.7", "", { "dependencies": { "ms": "^2.1.1" } }, "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ=="], "eslint-module-utils/debug": ["debug@3.2.7", "", { "dependencies": { "ms": "^2.1.1" } }, "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ=="], "eslint-plugin-import/debug": ["debug@3.2.7", "", { "dependencies": { "ms": "^2.1.1" } }, "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ=="], - "eslint-plugin-import/minimatch": ["minimatch@3.1.2", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw=="], + "eslint-plugin-import/minimatch": ["minimatch@3.1.5", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w=="], - "eslint-plugin-jsx-a11y/minimatch": ["minimatch@3.1.2", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw=="], + "eslint-plugin-import/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], - "eslint-plugin-react/minimatch": ["minimatch@3.1.2", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw=="], + "eslint-plugin-jsx-a11y/minimatch": ["minimatch@3.1.5", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w=="], + + "eslint-plugin-react/minimatch": ["minimatch@3.1.5", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w=="], + + "eslint-plugin-react/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], "fast-glob/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="], + "filelist/minimatch": ["minimatch@5.1.9", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw=="], + + "foreground-child/signal-exit": ["signal-exit@4.1.0", "", {}, "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw=="], + "has-ansi/ansi-regex": ["ansi-regex@2.1.1", "", {}, "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA=="], + "hosted-git-info/lru-cache": ["lru-cache@6.0.0", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA=="], + "loglevel-colored-level-prefix/chalk": ["chalk@1.1.3", "", { "dependencies": { "ansi-styles": "^2.2.1", "escape-string-regexp": "^1.0.2", "has-ansi": "^2.0.0", "strip-ansi": "^3.0.0", "supports-color": "^2.0.0" } }, "sha512-U3lRVLMSlsCfjqYPbLyVv11M9CPW4I728d6TCKMAOJueEeB9/8o+eSsMnxPJD+Q+K909sdESg7C+tIkoH6on1A=="], - "micromatch/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="], + "micromatch/picomatch": ["picomatch@2.3.2", "", {}, "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA=="], + + "minipass-flush/minipass": ["minipass@3.3.6", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw=="], + + "minipass-pipeline/minipass": ["minipass@3.3.6", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw=="], + + "minipass-sized/minipass": ["minipass@3.3.6", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw=="], "ml-random/ml-xsadd": ["ml-xsadd@2.0.0", "", {}, "sha512-VoAYUqmPRmzKbbqRejjqceGFp3VF81Qe8XXFGU0UXLxB7Mf4GGvyGq5Qn3k4AiQgDEV6WzobqlPOd+j0+m6IrA=="], + "node-exports-info/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], + + "postject/commander": ["commander@9.5.0", "", {}, "sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ=="], + "prettier-eslint/eslint": ["eslint@8.57.1", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", "@eslint/eslintrc": "^2.1.4", "@eslint/js": "8.57.1", "@humanwhocodes/config-array": "^0.13.0", "@humanwhocodes/module-importer": "^1.0.1", "@nodelib/fs.walk": "^1.2.8", "@ungap/structured-clone": "^1.2.0", "ajv": "^6.12.4", "chalk": "^4.0.0", "cross-spawn": "^7.0.2", "debug": "^4.3.2", "doctrine": "^3.0.0", "escape-string-regexp": "^4.0.0", "eslint-scope": "^7.2.2", "eslint-visitor-keys": "^3.4.3", "espree": "^9.6.1", "esquery": "^1.4.2", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^6.0.1", "find-up": "^5.0.0", "glob-parent": "^6.0.2", "globals": "^13.19.0", "graphemer": "^1.4.0", "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "is-path-inside": "^3.0.3", "js-yaml": "^4.1.0", "json-stable-stringify-without-jsonify": "^1.0.1", "levn": "^0.4.1", "lodash.merge": "^4.6.2", "minimatch": "^3.1.2", "natural-compare": "^1.4.0", "optionator": "^0.9.3", "strip-ansi": "^6.0.1", "text-table": "^0.2.0" }, "bin": { "eslint": "bin/eslint.js" } }, "sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA=="], - "pretty-format/react-is": ["react-is@18.3.1", "", {}, "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg=="], + "pretty-format/ansi-styles": ["ansi-styles@5.2.0", "", {}, "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA=="], - "react-i18next/use-sync-external-store": ["use-sync-external-store@1.6.0", "", { "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w=="], + "pretty-format/react-is": ["react-is@18.3.1", "", {}, "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg=="], "rimraf/glob": ["glob@7.2.3", "", { "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", "minimatch": "^3.1.1", "once": "^1.3.0", "path-is-absolute": "^1.0.0" } }, "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q=="], "rolldown/@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.0-rc.12", "", {}, "sha512-HHMwmarRKvoFsJorqYlFeFRzXZqCt2ETQlEDOb9aqssrnVBB1/+xgTGtuTrIk5vzLNX1MjMtTf7W9z3tsSbrxw=="], - "skia-canvas/detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="], + "serialize-error/type-fest": ["type-fest@0.13.1", "", {}, "sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg=="], - "tinyglobby/picomatch": ["picomatch@4.0.3", "", {}, "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q=="], + "string-width/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], - "typescript-eslint/@typescript-eslint/parser": ["@typescript-eslint/parser@8.57.2", "", { "dependencies": { "@typescript-eslint/scope-manager": "8.57.2", "@typescript-eslint/types": "8.57.2", "@typescript-eslint/typescript-estree": "8.57.2", "@typescript-eslint/visitor-keys": "8.57.2", "debug": "^4.4.3" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-30ScMRHIAD33JJQkgfGW1t8CURZtjc2JpTrq5n2HFhOefbAhb7ucc7xJwdWcrEtqUIYJ73Nybpsggii6GtAHjA=="], + "string-width-cjs/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], + + "temp/rimraf": ["rimraf@2.6.3", "", { "dependencies": { "glob": "^7.1.3" }, "bin": { "rimraf": "./bin.js" } }, "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA=="], + + "tiny-async-pool/semver": ["semver@5.7.2", "", { "bin": { "semver": "bin/semver" } }, "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g=="], + + "tsconfig-paths/json5": ["json5@1.0.2", "", { "dependencies": { "minimist": "^1.2.0" }, "bin": { "json5": "lib/cli.js" } }, "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA=="], + + "typescript-eslint/@typescript-eslint/parser": ["@typescript-eslint/parser@8.58.0", "", { "dependencies": { "@typescript-eslint/scope-manager": "8.58.0", "@typescript-eslint/types": "8.58.0", "@typescript-eslint/typescript-estree": "8.58.0", "@typescript-eslint/visitor-keys": "8.58.0", "debug": "^4.4.3" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-rLoGZIf9afaRBYsPUMtvkDWykwXwUPL60HebR4JgTI8mxfFe2cQTu3AGitANp4b9B2QlVru6WzjgB2IzJKiCSA=="], "vue-eslint-parser/eslint-scope": ["eslint-scope@7.2.2", "", { "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^5.2.0" } }, "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg=="], @@ -1251,57 +1734,77 @@ "vue-eslint-parser/espree": ["espree@9.6.1", "", { "dependencies": { "acorn": "^8.9.0", "acorn-jsx": "^5.3.2", "eslint-visitor-keys": "^3.4.1" } }, "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ=="], - "vue-eslint-parser/esquery": ["esquery@1.6.0", "", { "dependencies": { "estraverse": "^5.1.0" } }, "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg=="], + "@babel/helper-compilation-targets/lru-cache/yallist": ["yallist@3.1.1", "", {}, "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="], - "vue-eslint-parser/semver": ["semver@7.7.3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q=="], + "@electron/asar/minimatch/brace-expansion": ["brace-expansion@1.1.13", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-9ZLprWS6EENmhEOpjCYW2c8VkmOvckIJZfkr7rBW6dObmfgJ/L1GpSYW5Hpo9lDz4D1+n0Ckz8rU7FwHDQiG/w=="], - "@babel/helper-compilation-targets/browserslist/caniuse-lite": ["caniuse-lite@1.0.30001704", "", {}, "sha512-+L2IgBbV6gXB4ETf0keSvLr7JUrRVbIaB/lrQ1+z8mRcQiisG5k+lG6O4n6Y5q6f5EuNfaYXKgymucphlEXQew=="], + "@electron/get/fs-extra/jsonfile": ["jsonfile@4.0.0", "", { "optionalDependencies": { "graceful-fs": "^4.1.6" } }, "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg=="], - "@babel/helper-compilation-targets/browserslist/electron-to-chromium": ["electron-to-chromium@1.5.118", "", {}, "sha512-yNDUus0iultYyVoEFLnQeei7LOQkL8wg8GQpkPCRrOlJXlcCwa6eGKZkxQ9ciHsqZyYbj8Jd94X1CTPzGm+uIA=="], + "@electron/get/fs-extra/universalify": ["universalify@0.1.2", "", {}, "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg=="], - "@babel/helper-compilation-targets/browserslist/node-releases": ["node-releases@2.0.19", "", {}, "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw=="], + "@electron/universal/minimatch/brace-expansion": ["brace-expansion@2.0.3", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-MCV/fYJEbqx68aE58kv2cA/kiky1G8vux3OR6/jbS+jIMe/6fJWa0DTzJU7dqijOWYwHi1t29FlfYI9uytqlpA=="], - "@babel/helper-compilation-targets/browserslist/update-browserslist-db": ["update-browserslist-db@1.1.3", "", { "dependencies": { "escalade": "^3.2.0", "picocolors": "^1.1.1" }, "peerDependencies": { "browserslist": ">= 4.21.0" }, "bin": { "update-browserslist-db": "cli.js" } }, "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw=="], + "@eslint/eslintrc/espree/eslint-visitor-keys": ["eslint-visitor-keys@3.4.3", "", {}, "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag=="], - "@babel/helper-module-imports/@babel/traverse/@babel/generator": ["@babel/generator@7.28.0", "", { "dependencies": { "@babel/parser": "^7.28.0", "@babel/types": "^7.28.0", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-lJjzvrbEeWrhB4P3QBsH7tey117PjLZnDbLiQEKjQ/fNJTjuq4HSqgFA+UNSwZT8D7dxxbnuSBMsa1lrWzKlQg=="], + "@eslint/eslintrc/minimatch/brace-expansion": ["brace-expansion@1.1.13", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-9ZLprWS6EENmhEOpjCYW2c8VkmOvckIJZfkr7rBW6dObmfgJ/L1GpSYW5Hpo9lDz4D1+n0Ckz8rU7FwHDQiG/w=="], - "@babel/helper-module-imports/@babel/traverse/@babel/parser": ["@babel/parser@7.28.0", "", { "dependencies": { "@babel/types": "^7.28.0" }, "bin": "./bin/babel-parser.js" }, "sha512-jVZGvOxOuNSsuQuLRTh13nU0AogFlw32w/MT+LV6D3sP5WdbW61E77RnkbaO2dUvmPAYrBDJXGn5gGS6tH4j8g=="], + "@humanwhocodes/config-array/minimatch/brace-expansion": ["brace-expansion@1.1.13", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-9ZLprWS6EENmhEOpjCYW2c8VkmOvckIJZfkr7rBW6dObmfgJ/L1GpSYW5Hpo9lDz4D1+n0Ckz8rU7FwHDQiG/w=="], - "@babel/helper-module-imports/@babel/traverse/debug": ["debug@4.4.0", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA=="], + "@isaacs/cliui/strip-ansi/ansi-regex": ["ansi-regex@6.2.2", "", {}, "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg=="], - "@babel/helper-module-transforms/@babel/traverse/@babel/parser": ["@babel/parser@7.28.3", "", { "dependencies": { "@babel/types": "^7.28.2" }, "bin": "./bin/babel-parser.js" }, "sha512-7+Ey1mAgYqFAx2h0RuoxcQT5+MlG3GTV0TQrgr7/ZliKsm/MNDxVVutlWaziMq7wJNAz8MTqz55XLpWvva6StA=="], + "@isaacs/cliui/wrap-ansi/ansi-styles": ["ansi-styles@6.2.3", "", {}, "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg=="], - "@babel/helper-module-transforms/@babel/traverse/@babel/types": ["@babel/types@7.28.2", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.27.1" } }, "sha512-ruv7Ae4J5dUYULmeXw1gmb7rYRz57OWCPM57pHojnLq/3Z1CK2lNSLTCVjxVk1F/TZHwOZZrOWi0ur95BbLxNQ=="], + "@types/cacheable-request/@types/node/undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="], - "@babel/helper-module-transforms/@babel/traverse/debug": ["debug@4.4.0", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA=="], + "@types/fs-extra/@types/node/undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="], - "@eslint/eslintrc/espree/acorn": ["acorn@8.15.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg=="], + "@types/keyv/@types/node/undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="], - "@eslint/eslintrc/espree/eslint-visitor-keys": ["eslint-visitor-keys@3.4.3", "", {}, "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag=="], + "@types/plist/@types/node/undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="], - "@eslint/eslintrc/minimatch/brace-expansion": ["brace-expansion@1.1.11", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA=="], + "@types/responselike/@types/node/undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="], - "@humanwhocodes/config-array/minimatch/brace-expansion": ["brace-expansion@1.1.11", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA=="], + "@types/yauzl/@types/node/undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="], - "@typescript-eslint/eslint-plugin/@typescript-eslint/parser/@typescript-eslint/types": ["@typescript-eslint/types@8.57.2", "", {}, "sha512-/iZM6FnM4tnx9csuTxspMW4BOSegshwX5oBDznJ7S4WggL7Vczz5d2W11ecc4vRrQMQHXRSxzrCsyG5EsPPTbA=="], + "@typescript-eslint/eslint-plugin/@typescript-eslint/parser/@typescript-eslint/types": ["@typescript-eslint/types@8.58.0", "", {}, "sha512-O9CjxypDT89fbHxRfETNoAnHj/i6IpRK0CvbVN3qibxlLdo5p5hcLmUuCCrHMpxiWSwKyI8mCP7qRNYuOJ0Uww=="], - "@typescript-eslint/eslint-plugin/@typescript-eslint/scope-manager/@typescript-eslint/types": ["@typescript-eslint/types@8.57.2", "", {}, "sha512-/iZM6FnM4tnx9csuTxspMW4BOSegshwX5oBDznJ7S4WggL7Vczz5d2W11ecc4vRrQMQHXRSxzrCsyG5EsPPTbA=="], + "@typescript-eslint/eslint-plugin/@typescript-eslint/scope-manager/@typescript-eslint/types": ["@typescript-eslint/types@8.58.0", "", {}, "sha512-O9CjxypDT89fbHxRfETNoAnHj/i6IpRK0CvbVN3qibxlLdo5p5hcLmUuCCrHMpxiWSwKyI8mCP7qRNYuOJ0Uww=="], - "@typescript-eslint/eslint-plugin/@typescript-eslint/visitor-keys/@typescript-eslint/types": ["@typescript-eslint/types@8.57.2", "", {}, "sha512-/iZM6FnM4tnx9csuTxspMW4BOSegshwX5oBDznJ7S4WggL7Vczz5d2W11ecc4vRrQMQHXRSxzrCsyG5EsPPTbA=="], + "@typescript-eslint/eslint-plugin/@typescript-eslint/visitor-keys/@typescript-eslint/types": ["@typescript-eslint/types@8.58.0", "", {}, "sha512-O9CjxypDT89fbHxRfETNoAnHj/i6IpRK0CvbVN3qibxlLdo5p5hcLmUuCCrHMpxiWSwKyI8mCP7qRNYuOJ0Uww=="], "@typescript-eslint/parser/@typescript-eslint/typescript-estree/minimatch": ["minimatch@9.0.3", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg=="], - "@typescript-eslint/parser/@typescript-eslint/typescript-estree/semver": ["semver@7.7.3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q=="], - "@typescript-eslint/parser/@typescript-eslint/typescript-estree/ts-api-utils": ["ts-api-utils@1.4.3", "", { "peerDependencies": { "typescript": ">=4.2.0" } }, "sha512-i3eMG77UTMD0hZhgRS562pv83RC6ukSAC2GMNWc+9dieh/+jDM5u5YG+NHX6VNDRHQcHwmsTHctP9LhbC3WxVw=="], - "@typescript-eslint/utils/@typescript-eslint/scope-manager/@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.57.2", "", { "dependencies": { "@typescript-eslint/types": "8.57.2", "eslint-visitor-keys": "^5.0.0" } }, "sha512-zhahknjobV2FiD6Ee9iLbS7OV9zi10rG26odsQdfBO/hjSzUQbkIYgda+iNKK1zNiW2ey+Lf8MU5btN17V3dUw=="], + "@typescript-eslint/utils/@typescript-eslint/scope-manager/@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.58.0", "", { "dependencies": { "@typescript-eslint/types": "8.58.0", "eslint-visitor-keys": "^5.0.0" } }, "sha512-XJ9UD9+bbDo4a4epraTwG3TsNPeiB9aShrUneAVXy8q4LuwowN+qu89/6ByLMINqvIMeI9H9hOHQtg/ijrYXzQ=="], + + "app-builder-lib/@electron/get/fs-extra": ["fs-extra@8.1.0", "", { "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^4.0.0", "universalify": "^0.1.0" } }, "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g=="], + + "app-builder-lib/@electron/get/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], + + "cacache/glob/minimatch": ["minimatch@9.0.9", "", { "dependencies": { "brace-expansion": "^2.0.2" } }, "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg=="], + + "cacache/glob/path-scurry": ["path-scurry@1.11.1", "", { "dependencies": { "lru-cache": "^10.2.0", "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" } }, "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA=="], + + "cross-spawn/which/isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="], + + "dir-compare/minimatch/brace-expansion": ["brace-expansion@1.1.13", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-9ZLprWS6EENmhEOpjCYW2c8VkmOvckIJZfkr7rBW6dObmfgJ/L1GpSYW5Hpo9lDz4D1+n0Ckz8rU7FwHDQiG/w=="], + + "electron-winstaller/fs-extra/jsonfile": ["jsonfile@4.0.0", "", { "optionalDependencies": { "graceful-fs": "^4.1.6" } }, "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg=="], + + "electron-winstaller/fs-extra/universalify": ["universalify@0.1.2", "", {}, "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg=="], + + "electron/@types/node/undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="], + + "eslint-plugin-import/minimatch/brace-expansion": ["brace-expansion@1.1.13", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-9ZLprWS6EENmhEOpjCYW2c8VkmOvckIJZfkr7rBW6dObmfgJ/L1GpSYW5Hpo9lDz4D1+n0Ckz8rU7FwHDQiG/w=="], + + "eslint-plugin-jsx-a11y/minimatch/brace-expansion": ["brace-expansion@1.1.13", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-9ZLprWS6EENmhEOpjCYW2c8VkmOvckIJZfkr7rBW6dObmfgJ/L1GpSYW5Hpo9lDz4D1+n0Ckz8rU7FwHDQiG/w=="], - "eslint-plugin-import/minimatch/brace-expansion": ["brace-expansion@1.1.11", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA=="], + "eslint-plugin-react/minimatch/brace-expansion": ["brace-expansion@1.1.13", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-9ZLprWS6EENmhEOpjCYW2c8VkmOvckIJZfkr7rBW6dObmfgJ/L1GpSYW5Hpo9lDz4D1+n0Ckz8rU7FwHDQiG/w=="], - "eslint-plugin-jsx-a11y/minimatch/brace-expansion": ["brace-expansion@1.1.11", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA=="], + "filelist/minimatch/brace-expansion": ["brace-expansion@2.0.3", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-MCV/fYJEbqx68aE58kv2cA/kiky1G8vux3OR6/jbS+jIMe/6fJWa0DTzJU7dqijOWYwHi1t29FlfYI9uytqlpA=="], - "eslint-plugin-react/minimatch/brace-expansion": ["brace-expansion@1.1.11", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA=="], + "hosted-git-info/lru-cache/yallist": ["yallist@4.0.0", "", {}, "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="], "loglevel-colored-level-prefix/chalk/ansi-styles": ["ansi-styles@2.2.1", "", {}, "sha512-kmCevFghRiWM7HB5zTPULl4r9bVFSWjz62MhqizDGUrq2NWuNMQyuv4tHHoKJHs69M/MF64lEcHdYIocrdWQYA=="], @@ -1311,9 +1814,13 @@ "loglevel-colored-level-prefix/chalk/supports-color": ["supports-color@2.0.0", "", {}, "sha512-KKNVtd6pCYgPIKU4cp2733HWYCpplQhddZLBUryaAHou723x+FRzQ5Df824Fj+IyyuiQTRoub4SnIFfIcrp70g=="], - "prettier-eslint/eslint/@eslint/js": ["@eslint/js@8.57.1", "", {}, "sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q=="], + "minipass-flush/minipass/yallist": ["yallist@4.0.0", "", {}, "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="], + + "minipass-pipeline/minipass/yallist": ["yallist@4.0.0", "", {}, "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="], - "prettier-eslint/eslint/ajv": ["ajv@6.12.6", "", { "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", "json-schema-traverse": "^0.4.1", "uri-js": "^4.2.2" } }, "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g=="], + "minipass-sized/minipass/yallist": ["yallist@4.0.0", "", {}, "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="], + + "prettier-eslint/eslint/@eslint/js": ["@eslint/js@8.57.1", "", {}, "sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q=="], "prettier-eslint/eslint/doctrine": ["doctrine@3.0.0", "", { "dependencies": { "esutils": "^2.0.2" } }, "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w=="], @@ -1323,29 +1830,39 @@ "prettier-eslint/eslint/espree": ["espree@9.6.1", "", { "dependencies": { "acorn": "^8.9.0", "acorn-jsx": "^5.3.2", "eslint-visitor-keys": "^3.4.1" } }, "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ=="], - "prettier-eslint/eslint/esquery": ["esquery@1.6.0", "", { "dependencies": { "estraverse": "^5.1.0" } }, "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg=="], - "prettier-eslint/eslint/file-entry-cache": ["file-entry-cache@6.0.1", "", { "dependencies": { "flat-cache": "^3.0.4" } }, "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg=="], "prettier-eslint/eslint/globals": ["globals@13.24.0", "", { "dependencies": { "type-fest": "^0.20.2" } }, "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ=="], - "prettier-eslint/eslint/minimatch": ["minimatch@3.1.2", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw=="], + "prettier-eslint/eslint/minimatch": ["minimatch@3.1.5", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w=="], - "rimraf/glob/minimatch": ["minimatch@3.1.2", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw=="], + "rimraf/glob/minimatch": ["minimatch@3.1.5", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w=="], - "typescript-eslint/@typescript-eslint/parser/@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.57.2", "", { "dependencies": { "@typescript-eslint/types": "8.57.2", "@typescript-eslint/visitor-keys": "8.57.2" } }, "sha512-snZKH+W4WbWkrBqj4gUNRIGb/jipDW3qMqVJ4C9rzdFc+wLwruxk+2a5D+uoFcKPAqyqEnSb4l2ULuZf95eSkw=="], + "temp/rimraf/glob": ["glob@7.2.3", "", { "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", "minimatch": "^3.1.1", "once": "^1.3.0", "path-is-absolute": "^1.0.0" } }, "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q=="], - "typescript-eslint/@typescript-eslint/parser/@typescript-eslint/types": ["@typescript-eslint/types@8.57.2", "", {}, "sha512-/iZM6FnM4tnx9csuTxspMW4BOSegshwX5oBDznJ7S4WggL7Vczz5d2W11ecc4vRrQMQHXRSxzrCsyG5EsPPTbA=="], + "typescript-eslint/@typescript-eslint/parser/@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.58.0", "", { "dependencies": { "@typescript-eslint/types": "8.58.0", "@typescript-eslint/visitor-keys": "8.58.0" } }, "sha512-W1Lur1oF50FxSnNdGp3Vs6P+yBRSmZiw4IIjEeYxd8UQJwhUF0gDgDD/W/Tgmh73mxgEU3qX0Bzdl/NGuSPEpQ=="], - "typescript-eslint/@typescript-eslint/parser/@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.57.2", "", { "dependencies": { "@typescript-eslint/types": "8.57.2", "eslint-visitor-keys": "^5.0.0" } }, "sha512-zhahknjobV2FiD6Ee9iLbS7OV9zi10rG26odsQdfBO/hjSzUQbkIYgda+iNKK1zNiW2ey+Lf8MU5btN17V3dUw=="], + "typescript-eslint/@typescript-eslint/parser/@typescript-eslint/types": ["@typescript-eslint/types@8.58.0", "", {}, "sha512-O9CjxypDT89fbHxRfETNoAnHj/i6IpRK0CvbVN3qibxlLdo5p5hcLmUuCCrHMpxiWSwKyI8mCP7qRNYuOJ0Uww=="], - "vue-eslint-parser/espree/acorn": ["acorn@8.15.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg=="], + "typescript-eslint/@typescript-eslint/parser/@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.58.0", "", { "dependencies": { "@typescript-eslint/types": "8.58.0", "eslint-visitor-keys": "^5.0.0" } }, "sha512-XJ9UD9+bbDo4a4epraTwG3TsNPeiB9aShrUneAVXy8q4LuwowN+qu89/6ByLMINqvIMeI9H9hOHQtg/ijrYXzQ=="], + + "@electron/asar/minimatch/brace-expansion/balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], + + "@electron/universal/minimatch/brace-expansion/balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], "@eslint/eslintrc/minimatch/brace-expansion/balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], "@humanwhocodes/config-array/minimatch/brace-expansion/balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], - "@typescript-eslint/parser/@typescript-eslint/typescript-estree/minimatch/brace-expansion": ["brace-expansion@2.0.2", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ=="], + "@typescript-eslint/parser/@typescript-eslint/typescript-estree/minimatch/brace-expansion": ["brace-expansion@2.0.3", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-MCV/fYJEbqx68aE58kv2cA/kiky1G8vux3OR6/jbS+jIMe/6fJWa0DTzJU7dqijOWYwHi1t29FlfYI9uytqlpA=="], + + "app-builder-lib/@electron/get/fs-extra/jsonfile": ["jsonfile@4.0.0", "", { "optionalDependencies": { "graceful-fs": "^4.1.6" } }, "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg=="], + + "app-builder-lib/@electron/get/fs-extra/universalify": ["universalify@0.1.2", "", {}, "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg=="], + + "cacache/glob/minimatch/brace-expansion": ["brace-expansion@2.0.3", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-MCV/fYJEbqx68aE58kv2cA/kiky1G8vux3OR6/jbS+jIMe/6fJWa0DTzJU7dqijOWYwHi1t29FlfYI9uytqlpA=="], + + "dir-compare/minimatch/brace-expansion/balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], "eslint-plugin-import/minimatch/brace-expansion/balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], @@ -1353,20 +1870,28 @@ "eslint-plugin-react/minimatch/brace-expansion/balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], - "loglevel-colored-level-prefix/chalk/strip-ansi/ansi-regex": ["ansi-regex@2.1.1", "", {}, "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA=="], + "filelist/minimatch/brace-expansion/balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], - "prettier-eslint/eslint/espree/acorn": ["acorn@8.15.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg=="], + "loglevel-colored-level-prefix/chalk/strip-ansi/ansi-regex": ["ansi-regex@2.1.1", "", {}, "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA=="], "prettier-eslint/eslint/file-entry-cache/flat-cache": ["flat-cache@3.2.0", "", { "dependencies": { "flatted": "^3.2.9", "keyv": "^4.5.3", "rimraf": "^3.0.2" } }, "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw=="], - "prettier-eslint/eslint/minimatch/brace-expansion": ["brace-expansion@1.1.11", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA=="], + "prettier-eslint/eslint/minimatch/brace-expansion": ["brace-expansion@1.1.13", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-9ZLprWS6EENmhEOpjCYW2c8VkmOvckIJZfkr7rBW6dObmfgJ/L1GpSYW5Hpo9lDz4D1+n0Ckz8rU7FwHDQiG/w=="], - "rimraf/glob/minimatch/brace-expansion": ["brace-expansion@1.1.11", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA=="], + "rimraf/glob/minimatch/brace-expansion": ["brace-expansion@1.1.13", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-9ZLprWS6EENmhEOpjCYW2c8VkmOvckIJZfkr7rBW6dObmfgJ/L1GpSYW5Hpo9lDz4D1+n0Ckz8rU7FwHDQiG/w=="], + + "temp/rimraf/glob/minimatch": ["minimatch@3.1.5", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w=="], "@typescript-eslint/parser/@typescript-eslint/typescript-estree/minimatch/brace-expansion/balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], + "cacache/glob/minimatch/brace-expansion/balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], + "prettier-eslint/eslint/minimatch/brace-expansion/balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], "rimraf/glob/minimatch/brace-expansion/balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], + + "temp/rimraf/glob/minimatch/brace-expansion": ["brace-expansion@1.1.13", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-9ZLprWS6EENmhEOpjCYW2c8VkmOvckIJZfkr7rBW6dObmfgJ/L1GpSYW5Hpo9lDz4D1+n0Ckz8rU7FwHDQiG/w=="], + + "temp/rimraf/glob/minimatch/brace-expansion/balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], } } diff --git a/dev.mrquantumoff.mcmodpackmanager.metainfo.xml b/dev.mrquantumoff.mcmodpackmanager.metainfo.xml index 50f55af2..e66866ee 100644 --- a/dev.mrquantumoff.mcmodpackmanager.metainfo.xml +++ b/dev.mrquantumoff.mcmodpackmanager.metainfo.xml @@ -44,6 +44,14 @@ + + https://blog.mrquantumoff.dev + + Add Electron support as an alternative runtime + Dependency updates + + + https://blog.mrquantumoff.dev diff --git a/dev.mrquantumoff.mcmodpackmanager.yml b/dev.mrquantumoff.mcmodpackmanager.yml new file mode 100644 index 00000000..cffc5e87 --- /dev/null +++ b/dev.mrquantumoff.mcmodpackmanager.yml @@ -0,0 +1,122 @@ +# @format + +# Example Flatpak Manifest +# This repacks the published Electron AppImage into a Flatpak-friendly layout +# and launches it through the Electron BaseApp + zypak wrapper. +app-id: dev.mrquantumoff.mcmodpackmanager +runtime: org.freedesktop.Platform +runtime-version: "25.08" +sdk: org.freedesktop.Sdk +base: org.electronjs.Electron2.BaseApp +base-version: "25.08" +command: run_quadrant + +finish-args: + - --share=ipc + - --socket=wayland + - --socket=fallback-x11 + - --share=network + - --device=dri + - --filesystem=home + - --env=ELECTRON_TRASH=gio + - --env=XCURSOR_PATH=/run/host/user-share/icons:/run/host/share/icons + - --talk-name=org.freedesktop.secrets + - --talk-name=org.freedesktop.Notifications + - --talk-name=org.kde.StatusNotifierWatcher + +modules: + - shared-modules/libappindicator/libappindicator-gtk3-12.10.json + - name: Quadrant + buildsystem: simple + sources: + - type: file + url: https://github.com/quadrantmc/quadrant/raw/v26.4.0-stable/dev.mrquantumoff.mcmodpackmanager.metainfo.xml + sha256: b9b0cead668d8da6e5a151d66b59493eb54848a28d2fc5cee5616255ca0d2fe1 + # The release workflow replaces these placeholder hashes when Electron + # AppImages are available for a stable tag. + - type: file + url: https://github.com/quadrantmc/quadrant/releases/download/v26.4.0-stable/Quadrant-26.4.0-stable-linux-x64-electron.AppImage + sha256: 0000000000000000000000000000000000000000000000000000000000000000 + only-arches: [x86_64] + - type: file + url: https://github.com/quadrantmc/quadrant/releases/download/v26.4.0-stable/Quadrant-26.4.0-stable-linux-arm64-electron.AppImage + sha256: 0000000000000000000000000000000000000000000000000000000000000000 + only-arches: [aarch64] + + build-commands: + - | + set -eu + appimage="$(echo Quadrant-*-linux-*-electron.AppImage)" + chmod +x "$appimage" + ./"$appimage" --appimage-extract + - | + set -eu + desktop_file="$(find squashfs-root -name '*.desktop' | head -n 1)" + if [ -z "$desktop_file" ]; then + echo "Could not find a desktop file in the extracted AppImage." >&2 + exit 1 + fi + + exec_name="" + for candidate in squashfs-root/usr/bin/*; do + if [ -f "$candidate" ] && [ -x "$candidate" ]; then + exec_name="$(basename "$candidate")" + break + fi + done + + if [ -z "$exec_name" ]; then + exec_name="$(sed -n 's/^Exec=\([^[:space:]]*\).*/\1/p' "$desktop_file" | head -n 1)" + fi + + if [ -z "$exec_name" ]; then + echo "Could not determine the Electron executable name." >&2 + exit 1 + fi + + install -dm755 /app/lib/quadrant /app/bin + cp -a squashfs-root/. /app/lib/quadrant/ + + asar_file="$(find /app/lib/quadrant -path '*/resources/app.asar' | head -n 1)" + if [ -n "$asar_file" ]; then + patch-desktop-filename "$asar_file" + fi + + cat > /app/bin/run_quadrant <&2 + exit 1 + fi + ext="${fallback_icon##*.}" + install -Dm644 "$fallback_icon" "/app/share/icons/hicolor/512x512/apps/dev.mrquantumoff.mcmodpackmanager.${ext}" + fi + - install -Dm644 dev.mrquantumoff.mcmodpackmanager.metainfo.xml /app/share/metainfo/dev.mrquantumoff.mcmodpackmanager.metainfo.xml diff --git a/docs/backend-compatibility.md b/docs/backend-compatibility.md index 7cb074eb..9a69a96c 100644 --- a/docs/backend-compatibility.md +++ b/docs/backend-compatibility.md @@ -3,6 +3,7 @@ This document freezes the backend-facing surface while the Rust backend is extracted out of Tauri. ## Tauri Commands That Must Stay Stable + - `get_modpacks` - `frontend_apply_modpack` - `delete_mod` @@ -51,6 +52,7 @@ This document freezes the backend-facing surface while the Rust backend is extra - `remove_telemetry` ## Tauri Event Names That Must Stay Stable + - `modDownloadProgress` - `modInstallProgress` - `modpackDownloadProgress` @@ -63,12 +65,14 @@ This document freezes the backend-facing surface while the Rust backend is extra - `disableRightClick` ## Config And Storage Files That Must Stay Stable + - `config.json` - `updateConfig.json` - keyring service: `dev.mrquantumoff.mcmodpackmanager` - keyring keys: `accountToken`, `refreshToken` ## Filesystem Layout That Must Stay Stable + - Minecraft root from `config.json::mcFolder` - modpacks directory: `/modpacks` - active mods symlink or directory: `/mods` @@ -76,10 +80,12 @@ This document freezes the backend-facing surface while the Rust backend is extra - sync metadata: `/modpacks//quadrantSync.json` ## Sync Metadata Shape + - `quadrantSync.json` must continue to accept legacy `{ "last_synced": }` - current writers may also persist `{ "last_synced": , "modpack_id": "|null" }` ## Notifications Transport Notes + - live modpack sync is carried by `/api/v3/account/notifications/get` and `/api/v3/account/notifications/ws` - Quadrant opts into those events with `modpack_sync=true` - `/account/info/get` remains a profile endpoint and is not the live modpack sync transport diff --git a/docs/creating-a-quadrant-frontend.md b/docs/creating-a-quadrant-frontend.md index de0ae629..2d583c4d 100644 --- a/docs/creating-a-quadrant-frontend.md +++ b/docs/creating-a-quadrant-frontend.md @@ -200,7 +200,11 @@ export interface QuadrantBackendClient { getModpacks(hideFree: boolean): Promise; applyModpack(name: string): Promise; - createModpack(name: string, version: string, modLoader: ModLoader): Promise; + createModpack( + name: string, + version: string, + modLoader: ModLoader, + ): Promise; updateModpack(source: string, patch: Partial): Promise; deleteModpack(name: string): Promise; registerMod(mod: InstalledMod, modpack: string): Promise; @@ -215,7 +219,10 @@ export interface QuadrantBackendClient { getAccountInfo(): Promise; oauth2Login(code: string, redirectUri: string): Promise; - getSyncedModpacks(showOwners: boolean, modpackId?: string): Promise; + getSyncedModpacks( + showOwners: boolean, + modpackId?: string, + ): Promise; syncModpack(modpack: LocalModpack, overwrite: boolean): Promise; shareModpack(name: string): Promise; @@ -271,7 +278,10 @@ Example event envelope: ```ts type BackendEventEnvelope = - | { type: "modDownloadProgress"; payload: { modId: string; progress: number } } + | { + type: "modDownloadProgress"; + payload: { modId: string; progress: number }; + } | { type: "modInstallProgress"; payload: { modId: string; progress: number } } | { type: "modpackDownloadProgress"; payload: number } | { type: "quadrantExportProgress"; payload: number } diff --git a/docs/desktop-backends.md b/docs/desktop-backends.md new file mode 100644 index 00000000..fbae66e8 --- /dev/null +++ b/docs/desktop-backends.md @@ -0,0 +1,57 @@ +# Desktop Backends + +Quadrant supports two desktop shells that share one renderer and one backend contract. + +## Shared Architecture + +- The React renderer lives in `src/`. +- Renderer code should import the shared desktop API from `src/desktop/` instead of importing Tauri or Electron APIs directly. +- Runtime selection happens in `src/desktop/runtime.ts`. +- The backend command surface is the `QuadrantHost` contract exposed by `quadrant-host`. + +## Tauri Backend + +- Shell entrypoint: `src-tauri/` +- Renderer bridge: `src/desktop/tauri.ts` +- Backend implementation: Rust commands and plugins inside the Tauri application + +### Tauri Responsibilities + +- window management +- updater integration +- dialogs, filesystem watching, clipboard, deep links, and HTTP +- forwarding renderer command payloads into Rust commands + +## Electron Backend + +- Shell entrypoint: `electron/main.mjs` +- Preload bridge: `electron/preload.mjs` +- Renderer bridge: `src/desktop/electron.ts` +- Backend implementation: `@quadrant/quadrant-node` -> `quadrant-napi` -> `quadrant-host` + +### Electron Responsibilities + +- creating the main window and tray +- updater orchestration against the same Quadrant update endpoints used by Tauri +- store file persistence for `config.json` and `updateConfig.json` +- deep-link routing and OAuth callback server support +- forwarding `QuadrantHost` events back into the renderer + +### Linux Packaging + +- Electron release builds publish Linux AppImages for x64 and arm64. +- The Flatpak manifest repacks those AppImages through `org.electronjs.Electron2.BaseApp` and launches Quadrant through `zypak-wrapper`. +- The Flathub sync workflow rewrites the AppImage URLs and hashes in the manifest for each stable tag. + +## Build Matrix + +- `bun run dev:tauri` / `bun run build:tauri` +- `bun run dev:electron` / `bun run build:electron` +- `bun run build:napi` before packaged Electron builds when the native addon changes + +## Compatibility Rules + +- Renderer features should stay runtime-neutral. +- Command names and event names must remain stable across both backends. +- Persisted file names, app data layout, and keyring names must remain compatible. +- If a capability differs by shell, isolate the difference inside `src/desktop/*` or the shell entrypoint rather than branching inside feature components. diff --git a/electron-builder.json b/electron-builder.json new file mode 100644 index 00000000..2e07e4f1 --- /dev/null +++ b/electron-builder.json @@ -0,0 +1,48 @@ +{ + "appId": "dev.mrquantumoff.mcmodpackmanager", + "productName": "Quadrant", + "directories": { + "output": "dist-electron" + }, + "files": [ + "dist/**/*", + "electron/**/*", + "packages/quadrant-node/**/*", + "public/tray.png", + "package.json" + ], + "extraMetadata": { + "main": "electron/main.mjs" + }, + "asar": true, + "asarUnpack": ["packages/quadrant-node/native/**/*.node"], + "npmRebuild": false, + "buildDependenciesFromSource": false, + "protocols": [ + { + "name": "Quadrant", + "schemes": ["quadrantnext", "curseforge", "modrinth"] + } + ], + "publish": [ + { + "provider": "github", + "owner": "quadrantmc", + "repo": "quadrant" + } + ], + "artifactName": "Quadrant-${version}-${os}-${arch}-electron.${ext}", + "win": { + "icon": "src-tauri/icons/icon.ico", + "target": ["nsis", "zip"] + }, + "linux": { + "icon": "src-tauri/icons", + "target": ["AppImage", "deb"], + "category": "Utility" + }, + "nsis": { + "allowToChangeInstallationDirectory": true, + "oneClick": false + } +} diff --git a/electron/main.mjs b/electron/main.mjs new file mode 100644 index 00000000..f03f23e0 --- /dev/null +++ b/electron/main.mjs @@ -0,0 +1,726 @@ +/** @format */ + +import { + app, + BrowserWindow, + Menu, + Tray, + clipboard, + dialog, + ipcMain, + nativeTheme, + shell, +} from "electron"; +import electronUpdater from "electron-updater"; +import chokidar from "chokidar"; +import { createQuadrantClient } from "@quadrant/quadrant-node"; +import fs from "node:fs"; +import fsPromises from "node:fs/promises"; +import http from "node:http"; +import { randomUUID } from "node:crypto"; +import path from "node:path"; +import { fileURLToPath } from "node:url"; + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); +const rootDir = path.resolve(__dirname, ".."); +const appId = "dev.mrquantumoff.mcmodpackmanager"; +const schemes = ["quadrantnext", "curseforge", "modrinth"]; +const { autoUpdater } = electronUpdater; +const packageMetadata = JSON.parse( + fs.readFileSync(path.join(rootDir, "package.json"), "utf8"), +); +const quadrantAppVersion = + ( + typeof packageMetadata.version === "string" && + packageMetadata.version.trim().length > 0 + ) ? + packageMetadata.version.trim() + : app.getVersion(); + +app.setPath("userData", path.join(app.getPath("appData"), appId)); + +const argv = process.argv.slice(1); +const isAutostart = argv.includes("--autostart"); +const updaterDisabledByCli = argv.includes("--noupdater"); +const devServerUrl = process.env.QUADRANT_ELECTRON_DEV_SERVER_URL; + +let mainWindow = null; +let tray = null; +let quadrantClient = null; +let updaterConfigured = false; +let updateDownloaded = false; +const storeCache = new Map(); +const storeWriteQueues = new Map(); +const watchRegistry = new Map(); +const oauthServers = new Map(); +const pendingDeepLinks = []; +let openUrlRendererReady = false; + +function broadcast(channel, payload) { + for (const window of BrowserWindow.getAllWindows()) { + window.webContents.send(channel, payload); + } +} + +function parseDeepLinkUrls(values) { + return values.filter((value) => + schemes.some((scheme) => value.startsWith(`${scheme}:`)), + ); +} + +function flushPendingDeepLinks() { + if (!mainWindow || !openUrlRendererReady || pendingDeepLinks.length === 0) { + return; + } + const urls = pendingDeepLinks.splice(0, pendingDeepLinks.length); + mainWindow.webContents.send("quadrant:open-url", urls); +} + +function loadGeneratedRuntimeConfig() { + const runtimeConfigPath = path.join( + rootDir, + "electron", + "runtime-config.generated.json", + ); + + if (!fs.existsSync(runtimeConfigPath)) { + return {}; + } + + return JSON.parse(fs.readFileSync(runtimeConfigPath, "utf8")); +} + +function normalizeOptionalConfigValue(value) { + if (typeof value !== "string") { + return value ?? null; + } + + const trimmed = value.trim(); + return trimmed.length > 0 ? trimmed : null; +} + +function normalizeRequiredConfigValue(value) { + if (typeof value !== "string") { + return ""; + } + + return value.trim(); +} + +function getRuntimeConfig() { + const generated = loadGeneratedRuntimeConfig(); + return { + oauthClientId: normalizeRequiredConfigValue( + process.env.QUADRANT_OAUTH2_CLIENT_ID ?? generated.oauthClientId, + ), + oauthClientSecret: normalizeRequiredConfigValue( + process.env.QUADRANT_OAUTH2_CLIENT_SECRET ?? generated.oauthClientSecret, + ), + quadrantApiKey: normalizeRequiredConfigValue( + process.env.QUADRANT_API_KEY ?? generated.quadrantApiKey, + ), + apiBaseUrl: normalizeOptionalConfigValue( + process.env.QUADRANT_API_BASE_URL ?? generated.apiBaseUrl, + ), + }; +} + +function ensureRuntimeSecrets(config) { + if ( + !config.oauthClientId || + !config.oauthClientSecret || + !config.quadrantApiKey + ) { + throw new Error( + "Electron runtime config is incomplete. Run with QUADRANT_OAUTH2_CLIENT_ID, QUADRANT_OAUTH2_CLIENT_SECRET, and QUADRANT_API_KEY available.", + ); + } +} + +function getWindow() { + if (!mainWindow) { + throw new Error("Main window is not ready"); + } + return mainWindow; +} + +function resolveIconPath() { + return path.join(rootDir, "src-tauri", "icons", "128x128.png"); +} + +function resolveTrayIconPath() { + const trayIconPath = path.join(rootDir, "public", "tray.png"); + return fs.existsSync(trayIconPath) ? trayIconPath : resolveIconPath(); +} + +function normalizeDesktopPlatform(platform) { + switch (platform) { + case "win32": + return "windows"; + case "darwin": + return "macos"; + default: + return platform; + } +} + +function storeFilePath(storeName) { + return path.join(app.getPath("userData"), storeName); +} + +async function readStore(storeName) { + const filePath = storeFilePath(storeName); + await fsPromises.mkdir(path.dirname(filePath), { recursive: true }); + + let data = {}; + if (fs.existsSync(filePath)) { + try { + data = JSON.parse(await fsPromises.readFile(filePath, "utf8")); + } catch (error) { + if (storeCache.has(storeName)) { + console.warn(`Failed to parse ${filePath}, using cached copy`, error); + return storeCache.get(storeName); + } + console.warn(`Failed to parse ${filePath}`, error); + } + } + + storeCache.set(storeName, data); + return data; +} + +async function writeStoreFile(filePath, data) { + const tempPath = `${filePath}.${randomUUID()}.tmp`; + await fsPromises.writeFile(tempPath, JSON.stringify(data, null, 2)); + await fsPromises.rename(tempPath, filePath); +} + +async function saveStore(storeName, dataOverride) { + const data = + dataOverride ?? storeCache.get(storeName) ?? (await readStore(storeName)); + const filePath = storeFilePath(storeName); + await fsPromises.mkdir(path.dirname(filePath), { recursive: true }); + await writeStoreFile(filePath, data); +} + +function queueStoreWrite(storeName, operation) { + const previous = storeWriteQueues.get(storeName) ?? Promise.resolve(); + const next = previous.catch(() => {}).then(operation); + storeWriteQueues.set(storeName, next); + return next.finally(() => { + if (storeWriteQueues.get(storeName) === next) { + storeWriteQueues.delete(storeName); + } + }); +} + +async function setStoreValue(storeName, key, value) { + await queueStoreWrite(storeName, async () => { + const data = await readStore(storeName); + data[key] = value; + storeCache.set(storeName, data); + await saveStore(storeName, data); + }); + broadcast("quadrant:store:changed", { storeName, key, value }); +} + +function queueDeepLinks(urls) { + if (urls.length === 0) { + return; + } + pendingDeepLinks.push(...urls); + if (mainWindow && openUrlRendererReady) { + flushPendingDeepLinks(); + } +} + +async function createQuadrantHostClient() { + if (quadrantClient) { + return quadrantClient; + } + + // Electron talks to the same QuadrantHost backend contract as Tauri, but it + // instantiates it through the N-API addon instead of Rust invoke handlers. + const runtimeConfig = getRuntimeConfig(); + ensureRuntimeSecrets(runtimeConfig); + + quadrantClient = createQuadrantClient({ + dataDir: app.getPath("userData"), + apiBaseUrl: runtimeConfig.apiBaseUrl, + oauthClientId: runtimeConfig.oauthClientId, + oauthClientSecret: runtimeConfig.oauthClientSecret, + quadrantApiKey: runtimeConfig.quadrantApiKey, + appVersion: quadrantAppVersion, + osName: process.platform.toUpperCase(), + userAgent: `mrquantumoff/quadrant/${quadrantAppVersion} (mrquantumoff.dev) (QUADRANT NEXT/Electron v${process.versions.electron})`, + }); + + quadrantClient.on("event", (event) => { + broadcast("quadrant:backend-event", event); + }); + + await quadrantClient.initConfig(); + try { + await quadrantClient.startBackgroundWorkers(); + } catch (error) { + console.error("Failed to start background workers", error); + } + + quadrantClient + .invoke("get_versions") + .catch((error) => + console.warn("Failed to warm Minecraft versions cache", error), + ); + + return quadrantClient; +} + +async function configureUpdater() { + const updateStore = await readStore("updateConfig.json"); + const channel = updateStore.channel ?? "stable"; + autoUpdater.allowPrerelease = channel !== "stable"; + + if (updaterConfigured) { + return; + } + + autoUpdater.autoDownload = true; + autoUpdater.autoInstallOnAppQuit = false; + updaterConfigured = true; + + autoUpdater.on("download-progress", (progress) => { + broadcast("quadrant:backend-event", { + event: "updateDownloadProgress", + payload: progress.percent / 100, + }); + }); + + autoUpdater.on("update-downloaded", () => { + updateDownloaded = true; + broadcast("quadrant:backend-event", { + event: "updateDownloadProgress", + payload: 1, + }); + }); + + autoUpdater.on("update-not-available", () => { + updateDownloaded = false; + }); +} + +function isAutoupdateEnabled() { + return app.isPackaged && !updaterDisabledByCli; +} + +async function requestCheckForUpdates() { + if (!isAutoupdateEnabled()) { + return; + } + + await configureUpdater(); + await autoUpdater.checkForUpdates(); +} + +function createMainWindow() { + openUrlRendererReady = false; + nativeTheme.themeSource = "dark"; + + const window = new BrowserWindow({ + title: "Quadrant", + width: 1280, + height: 720, + minWidth: 1280, + minHeight: 720, + show: false, + frame: false, + backgroundColor: "#020617", + icon: resolveIconPath(), + webPreferences: { + preload: path.join(__dirname, "preload.mjs"), + contextIsolation: true, + nodeIntegration: false, + sandbox: false, + }, + }); + + window.on("ready-to-show", () => { + if (isAutostart) { + window.hide(); + return; + } + window.show(); + }); + + window.on("closed", () => { + if (mainWindow === window) { + openUrlRendererReady = false; + mainWindow = null; + } + }); + + window.webContents.on("did-start-loading", () => { + if (mainWindow === window) { + openUrlRendererReady = false; + } + }); + + if (devServerUrl) { + window.loadURL(devServerUrl); + window.webContents.openDevTools({ mode: "detach" }); + } else { + window.loadFile(path.join(rootDir, "dist", "index.html")); + } + + return window; +} + +function showMainWindow() { + if (!mainWindow) { + return; + } + mainWindow.show(); + mainWindow.focus(); + if (mainWindow.isMinimized()) { + mainWindow.restore(); + } +} + +function toggleMainWindowVisibility() { + if (!mainWindow) { + return; + } + + if (mainWindow.isVisible()) { + mainWindow.hide(); + } else { + showMainWindow(); + } +} + +function createTray() { + const nextTray = new Tray(resolveTrayIconPath()); + const menu = Menu.buildFromTemplate([ + { + label: "Show/Hide", + click: () => { + toggleMainWindowVisibility(); + }, + }, + { + label: "Quit", + click: () => { + app.quit(); + }, + }, + ]); + + nextTray.setToolTip("Quadrant"); + nextTray.setContextMenu(menu); + nextTray.on("click", () => { + toggleMainWindowVisibility(); + }); + + return nextTray; +} + +function registerProtocolHandlers() { + for (const scheme of schemes) { + if (process.defaultApp) { + if (process.argv.length >= 2) { + app.setAsDefaultProtocolClient(scheme, process.execPath, [ + path.resolve(process.argv[1]), + ]); + } + continue; + } + app.setAsDefaultProtocolClient(scheme); + } +} + +const gotLock = app.requestSingleInstanceLock(); +if (!gotLock) { + app.quit(); +} + +queueDeepLinks(parseDeepLinkUrls(process.argv)); + +app.on("second-instance", (_event, commandLine) => { + showMainWindow(); + queueDeepLinks(parseDeepLinkUrls(commandLine)); +}); + +app.on("open-url", (event, url) => { + event.preventDefault(); + queueDeepLinks([url]); +}); + +app.whenReady().then(async () => { + registerProtocolHandlers(); + mainWindow = createMainWindow(); + tray = createTray(); + + await createQuadrantHostClient(); +}); + +app.on("window-all-closed", () => { + if (process.platform !== "darwin") { + app.quit(); + } +}); + +app.on("activate", () => { + if (BrowserWindow.getAllWindows().length === 0) { + mainWindow = createMainWindow(); + return; + } + showMainWindow(); +}); + +app.on("before-quit", async () => { + for (const watcher of watchRegistry.values()) { + await watcher.close(); + } + watchRegistry.clear(); + + for (const server of oauthServers.values()) { + server.close(); + } + oauthServers.clear(); + + if (quadrantClient) { + try { + await quadrantClient.shutdown(); + } catch (error) { + console.error("Failed to shut down Quadrant client", error); + } + } +}); + +ipcMain.handle("quadrant:invoke", async (_event, { command, payload }) => { + const client = await createQuadrantHostClient(); + return client.invoke(command, payload ?? null); +}); + +ipcMain.handle("quadrant:store:get", async (_event, { storeName, key }) => { + const store = await readStore(storeName); + return store[key]; +}); + +ipcMain.handle( + "quadrant:store:set", + async (_event, { storeName, key, value }) => { + await setStoreValue(storeName, key, value); + }, +); + +ipcMain.handle("quadrant:store:save", async (_event, { storeName }) => { + await queueStoreWrite(storeName, async () => { + await saveStore(storeName); + }); +}); + +ipcMain.handle( + "quadrant:fs-watch:start", + async (event, { watchId, targetPath, options }) => { + const watcher = chokidar.watch(targetPath, { + ignoreInitial: true, + awaitWriteFinish: + options?.delayMs ? + { + stabilityThreshold: options.delayMs, + pollInterval: Math.max(50, Math.floor(options.delayMs / 2)), + } + : false, + }); + const sendChange = () => { + event.sender.send("quadrant:fs-watch:event", { watchId }); + }; + watcher.on("add", sendChange); + watcher.on("change", sendChange); + watcher.on("unlink", sendChange); + watcher.on("addDir", sendChange); + watcher.on("unlinkDir", sendChange); + watchRegistry.set(watchId, watcher); + }, +); + +ipcMain.handle("quadrant:fs-watch:stop", async (_event, { watchId }) => { + const watcher = watchRegistry.get(watchId); + if (watcher) { + await watcher.close(); + watchRegistry.delete(watchId); + } +}); + +ipcMain.handle("quadrant:path:join", async (_event, { segments }) => { + return path.join(...segments); +}); + +ipcMain.handle("quadrant:dialog:open", async (_event, { options }) => { + if (options?.mode === "save") { + const result = await dialog.showSaveDialog(getWindow(), { + title: options?.title, + defaultPath: options?.defaultPath, + showOverwriteConfirmation: true, + }); + + if (result.canceled) { + return null; + } + + return result.filePath ?? null; + } + + const result = await dialog.showOpenDialog(getWindow(), { + title: options?.title, + properties: [ + options?.directory ? "openDirectory" : "openFile", + ...(options?.multiple ? ["multiSelections"] : []), + ...(options?.directory && options?.recursive ? ["createDirectory"] : []), + ], + defaultPath: options?.defaultPath, + }); + + if (result.canceled) { + return null; + } + + if (options?.multiple) { + return result.filePaths; + } + + return result.filePaths[0] ?? null; +}); + +ipcMain.handle("quadrant:open-url:listener-ready", (event) => { + if (mainWindow && event.sender.id === mainWindow.webContents.id) { + openUrlRendererReady = true; + flushPendingDeepLinks(); + } +}); + +ipcMain.handle("quadrant:shell:open-external", async (_event, { url }) => { + await shell.openExternal(url); +}); + +ipcMain.handle("quadrant:shell:open-path", async (_event, { targetPath }) => { + const errorMessage = await shell.openPath(targetPath); + if (errorMessage) { + throw new Error(errorMessage); + } +}); + +ipcMain.handle("quadrant:clipboard:read-text", async () => + clipboard.readText(), +); +ipcMain.handle("quadrant:clipboard:write-text", async (_event, { text }) => { + clipboard.writeText(text); +}); + +ipcMain.handle("quadrant:platform", async () => + normalizeDesktopPlatform(process.platform), +); +ipcMain.handle("quadrant:app-version", async () => quadrantAppVersion); +ipcMain.handle( + "quadrant:runtime-version", + async () => process.versions.electron, +); + +ipcMain.handle("quadrant:updater:check", async () => { + await requestCheckForUpdates(); +}); + +ipcMain.handle("quadrant:updater:install", async () => { + if (!updateDownloaded) { + throw new Error("No downloaded update is available"); + } + autoUpdater.quitAndInstall(); +}); + +ipcMain.handle("quadrant:updater:is-enabled", async () => + isAutoupdateEnabled(), +); + +ipcMain.handle("quadrant:window:minimize", async () => { + getWindow().minimize(); +}); + +ipcMain.handle("quadrant:window:hide", async () => { + getWindow().hide(); +}); + +ipcMain.handle("quadrant:window:set-enabled", async (_event, { enabled }) => { + getWindow().setEnabled(enabled); +}); + +ipcMain.handle("quadrant:window:set-focus", async () => { + getWindow().focus(); +}); + +ipcMain.handle("quadrant:window:unminimize", async () => { + const window = getWindow(); + if (window.isMinimized()) { + window.restore(); + } +}); + +ipcMain.handle( + "quadrant:window:set-progress-bar", + async (_event, { state }) => { + const window = getWindow(); + const normalizedProgress = + typeof state.progress === "number" && state.progress <= 1 ? + state.progress + : state.progress / 100; + const progress = state.status === "none" ? -1 : normalizedProgress; + window.setProgressBar(progress); + }, +); + +ipcMain.handle("quadrant:oauth:start", async (_event, { options }) => { + const responseHtml = + options?.response ?? + "You can return to Quadrant now."; + const ports = options?.ports ?? [4000, 4001, 4002, 4003, 4004, 4005]; + + for (const port of ports) { + try { + const server = http.createServer((request, response) => { + const url = `http://127.0.0.1:${port}${request.url ?? "/"}`; + broadcast("quadrant:oauth:url", url); + response.writeHead(200, { "Content-Type": "text/html; charset=utf-8" }); + response.end(responseHtml); + }); + + await new Promise((resolve, reject) => { + server.once("error", reject); + server.listen(port, "127.0.0.1", () => resolve(undefined)); + }); + + oauthServers.set(port, server); + return port; + } catch (error) { + console.warn(`Failed to bind OAuth server on ${port}`, error); + } + } + + throw new Error("No available OAuth callback port"); +}); + +ipcMain.handle("quadrant:oauth:cancel", async (_event, { port }) => { + const server = oauthServers.get(port); + if (!server) { + return; + } + + await new Promise((resolve, reject) => { + server.close((error) => { + if (error) { + reject(error); + return; + } + resolve(undefined); + }); + }); + oauthServers.delete(port); +}); diff --git a/electron/preload.mjs b/electron/preload.mjs new file mode 100644 index 00000000..6686340f --- /dev/null +++ b/electron/preload.mjs @@ -0,0 +1,120 @@ +import { contextBridge, ipcRenderer } from "electron"; +import { randomUUID } from "node:crypto"; + +function listen(channel, listener) { + const wrapped = (_event, payload) => listener(payload); + ipcRenderer.on(channel, wrapped); + return () => { + ipcRenderer.removeListener(channel, wrapped); + }; +} + +const api = { + invoke(command, payload) { + return ipcRenderer.invoke("quadrant:invoke", { command, payload }); + }, + addBackendEventListener(listener) { + return listen("quadrant:backend-event", listener); + }, + storeGet(storeName, key) { + return ipcRenderer.invoke("quadrant:store:get", { storeName, key }); + }, + storeSet(storeName, key, value) { + return ipcRenderer.invoke("quadrant:store:set", { storeName, key, value }); + }, + storeSave(storeName) { + return ipcRenderer.invoke("quadrant:store:save", { storeName }); + }, + addStoreChangeListener(listener) { + return listen("quadrant:store:changed", listener); + }, + async watchPath(targetPath, options, listener) { + const watchId = randomUUID(); + const stopListening = listen("quadrant:fs-watch:event", (payload) => { + if (payload.watchId === watchId) { + listener(); + } + }); + await ipcRenderer.invoke("quadrant:fs-watch:start", { + watchId, + targetPath, + options, + }); + return async () => { + stopListening(); + await ipcRenderer.invoke("quadrant:fs-watch:stop", { watchId }); + }; + }, + joinPath(...segments) { + return ipcRenderer.invoke("quadrant:path:join", { segments }); + }, + openDialog(options) { + return ipcRenderer.invoke("quadrant:dialog:open", { options }); + }, + openExternal(url) { + return ipcRenderer.invoke("quadrant:shell:open-external", { url }); + }, + openPath(targetPath) { + return ipcRenderer.invoke("quadrant:shell:open-path", { targetPath }); + }, + readClipboardText() { + return ipcRenderer.invoke("quadrant:clipboard:read-text"); + }, + writeClipboardText(text) { + return ipcRenderer.invoke("quadrant:clipboard:write-text", { text }); + }, + platform() { + return ipcRenderer.invoke("quadrant:platform"); + }, + getAppVersion() { + return ipcRenderer.invoke("quadrant:app-version"); + }, + getRuntimeVersion() { + return ipcRenderer.invoke("quadrant:runtime-version"); + }, + requestCheckForUpdates() { + return ipcRenderer.invoke("quadrant:updater:check"); + }, + installUpdate() { + return ipcRenderer.invoke("quadrant:updater:install"); + }, + isAutoupdateEnabled() { + return ipcRenderer.invoke("quadrant:updater:is-enabled"); + }, + windowMinimize() { + return ipcRenderer.invoke("quadrant:window:minimize"); + }, + windowHide() { + return ipcRenderer.invoke("quadrant:window:hide"); + }, + windowSetEnabled(enabled) { + return ipcRenderer.invoke("quadrant:window:set-enabled", { enabled }); + }, + windowSetFocus() { + return ipcRenderer.invoke("quadrant:window:set-focus"); + }, + windowUnminimize() { + return ipcRenderer.invoke("quadrant:window:unminimize"); + }, + windowSetProgressBar(state) { + return ipcRenderer.invoke("quadrant:window:set-progress-bar", { state }); + }, + addOpenUrlListener(listener) { + const unlisten = listen("quadrant:open-url", listener); + ipcRenderer.invoke("quadrant:open-url:listener-ready").catch((error) => { + console.error("Failed to mark open-url listener as ready", error); + }); + return unlisten; + }, + startOAuthServer(options) { + return ipcRenderer.invoke("quadrant:oauth:start", { options }); + }, + cancelOAuthServer(port) { + return ipcRenderer.invoke("quadrant:oauth:cancel", { port }); + }, + addOAuthUrlListener(listener) { + return listen("quadrant:oauth:url", listener); + }, +}; + +contextBridge.exposeInMainWorld("quadrantElectron", api); diff --git a/electron/tsconfig.json b/electron/tsconfig.json new file mode 100644 index 00000000..1a96a3a8 --- /dev/null +++ b/electron/tsconfig.json @@ -0,0 +1,12 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "NodeNext", + "moduleResolution": "NodeNext", + "allowJs": true, + "checkJs": false, + "types": ["node"], + "skipLibCheck": true + }, + "include": ["./*.mjs"] +} diff --git a/eslint.config.js b/eslint.config.js index a2d989c2..ba8a7d69 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -9,74 +9,74 @@ import css from "@eslint/css"; import { defineConfig } from "eslint/config"; export default defineConfig([ - { - ignores: ["src-tauri/target/**", "**/*.css"], - }, + { + ignores: ["src-tauri/target/**", "**/*.css"], + }, - // Base JS - js.configs.recommended, + // Base JS + js.configs.recommended, - // TypeScript - ...tseslint.configs.recommended, + // TypeScript + ...tseslint.configs.recommended, - // React - { - files: ["**/*.{jsx,tsx}"], - plugins: { - react, - "react-hooks": reactHooks, - }, - rules: { - ...react.configs.recommended.rules, - "react-hooks/rules-of-hooks": "error", - "react-hooks/exhaustive-deps": "warn", - "react/react-in-jsx-scope": "off", - }, - settings: { - react: { - version: "19", - }, - }, - }, + // React + { + files: ["**/*.{jsx,tsx}"], + plugins: { + react, + "react-hooks": reactHooks, + }, + rules: { + ...react.configs.recommended.rules, + "react-hooks/rules-of-hooks": "error", + "react-hooks/exhaustive-deps": "warn", + "react/react-in-jsx-scope": "off", + }, + settings: { + react: { + version: "19", + }, + }, + }, - // Browser globals - { - files: ["**/*.{js,mjs,cjs,ts,mts,cts,jsx,tsx}"], - languageOptions: { - parserOptions: { - ecmaFeatures: { - jsx: true, - }, - }, - globals: { - ...globals.browser, - }, - }, - }, - { - files: ["**/*.cjs"], - languageOptions: { - globals: { - ...globals.node, - module: "writable", - require: "readonly", - }, - }, - }, + // Browser globals + { + files: ["**/*.{js,mjs,cjs,ts,mts,cts,jsx,tsx}"], + languageOptions: { + parserOptions: { + ecmaFeatures: { + jsx: true, + }, + }, + globals: { + ...globals.browser, + }, + }, + }, + { + files: ["**/*.cjs"], + languageOptions: { + globals: { + ...globals.node, + module: "writable", + require: "readonly", + }, + }, + }, - // CSS - { - files: ["**/*.css"], - plugins: { - css, - }, - rules: { - ...css.configs.recommended.rules, - }, - }, - { - rules: { - "@typescript-eslint/no-explicit-any": "off", - }, - }, + // CSS + { + files: ["**/*.css"], + plugins: { + css, + }, + rules: { + ...css.configs.recommended.rules, + }, + }, + { + rules: { + "@typescript-eslint/no-explicit-any": "off", + }, + }, ]); diff --git a/index.html b/index.html index 5fdf83e8..fa2bcfe6 100644 --- a/index.html +++ b/index.html @@ -1,4 +1,4 @@ - + diff --git a/package.json b/package.json index 14e05382..09d33ea5 100644 --- a/package.json +++ b/package.json @@ -1,51 +1,68 @@ { "name": "quadrant-next", "private": true, - "version": "26.4.0", + "version": "26.4.1-preview.0", "type": "module", + "description": "An easy way to manage your Minecraft mods and modpacks.", + "author": "Demir Yerli ", + "workspaces": [ + "packages/*" + ], "scripts": { "dev": "vite", "build": "tsc && vite build", "lint": "eslint .", "preview": "vite preview", + "build:napi": "node scripts/build-napi.mjs", + "dev:tauri": "tauri dev", + "build:tauri": "tauri build", + "dev:electron": "node scripts/dev-electron.mjs", + "build:electron": "node scripts/build-electron.mjs", + "package:electron": "node scripts/package-electron.mjs", "tauri": "tauri", "tauri:windows:build": "tauri-windows-bundle build" }, "dependencies": { + "@quadrant/quadrant-node": "workspace:*", "@fabianlars/tauri-plugin-oauth": "^2.0.0", "@headlessui/react": "^2.2.9", "@tauri-apps/api": "^2.10.1", "@tauri-apps/plugin-autostart": "~2.5.1", "@tauri-apps/plugin-cli": "~2.4.1", "@tauri-apps/plugin-clipboard-manager": "2.3.2", - "@tauri-apps/plugin-deep-link": "~2.4.7", - "@tauri-apps/plugin-dialog": "~2.6.0", - "@tauri-apps/plugin-fs": "~2.4.5", - "@tauri-apps/plugin-http": "~2.5.7", + "@tauri-apps/plugin-deep-link": "~2.4.8", + "@tauri-apps/plugin-dialog": "~2.7.0", + "@tauri-apps/plugin-fs": "~2.5.0", + "@tauri-apps/plugin-http": "~2.5.8", "@tauri-apps/plugin-notification": "2.3.3", "@tauri-apps/plugin-opener": "2.5.3", "@tauri-apps/plugin-os": "~2.3.2", "@tauri-apps/plugin-store": "2.4.2", - "@tauri-apps/plugin-updater": "2.10.0", - "i18next": "26.0.1", + "@tauri-apps/plugin-updater": "2.10.1", + "i18next": "26.0.3", "motion": "12.38.0", "react": "19.2.4", "react-dom": "19.2.4", - "react-i18next": "^17.0.1", + "react-i18next": "^17.0.2", "react-icons": "^5.6.0" }, "devDependencies": { - "@eslint/css": "^1.0.0", + "@eslint/css": "^1.1.0", "@eslint/js": "^10.0.1", "@eslint/json": "^1.2.0", "@tailwindcss/postcss": "4.2.2", + "chokidar": "^5.0.0", + "electron": "^41.1.1", + "electron-builder": "^26.8.1", + "electron-updater": "^6.8.3", "@tauri-apps/cli": "^2.10.1", + "@types/node": "^25.5.2", "@types/react": "^19.2.14", "@types/react-dom": "^19.2.3", "@vitejs/plugin-react": "^6.0.1", "autoprefixer": "^10.4.27", "babel-plugin-react-compiler": "1.0.0", - "eslint": "^10.1.0", + "eslint": "^10.2.0", "eslint-config-prettier": "^10.1.8", "eslint-plugin-import": "^2.32.0", "eslint-plugin-jsx-a11y": "^6.10.2", @@ -58,17 +75,13 @@ "prettier-eslint": "^16.4.2", "tailwindcss": "4.2.2", "typescript": "6.0.2", - "typescript-eslint": "^8.57.2", + "typescript-eslint": "^8.58.0", "vite": "^8.0.3", "@choochmeque/tauri-windows-bundle": "^0.1.17" }, "packageManager": "bun@1.3.11", - "pnpm": { - "onlyBuiltDependencies": [ - "esbuild" - ] - }, "trustedDependencies": [ - "@tailwindcss/oxide" + "@tailwindcss/oxide", + "electron" ] -} \ No newline at end of file +} diff --git a/packages/quadrant-node/index.d.ts b/packages/quadrant-node/index.d.ts new file mode 100644 index 00000000..bf31c2b3 --- /dev/null +++ b/packages/quadrant-node/index.d.ts @@ -0,0 +1,41 @@ +export interface QuadrantClientOptions { + dataDir: string; + mcFolder?: string | null; + apiBaseUrl?: string | null; + oauthClientId: string; + oauthClientSecret: string; + quadrantApiKey: string; + configStoreName?: string | null; + updateStoreName?: string | null; + keyringServiceName?: string | null; + appVersion?: string | null; + osName?: string | null; + userAgent?: string | null; +} + +export interface QuadrantEventEnvelope { + event: string; + payload: unknown; +} + +export interface QuadrantClient { + startBackgroundWorkers(): Promise; + stopBackgroundWorkers(): Promise; + shutdown(): Promise; + initConfig(): Promise; + getMinecraftFolder(): Promise; + invoke(command: string, payload?: unknown): Promise; + getModpacks(hideFree?: boolean): Promise; + getAccountInfo(): Promise; + getNews(): Promise; + installMod(args: unknown): Promise; + syncModpack(modpack: unknown, overwrite?: boolean): Promise; + on(eventName: string, listener: (payload: unknown) => void): () => void; + off(eventName: string, listener: (payload: unknown) => void): void; + once(eventName: string, listener: (payload: unknown) => void): void; +} + +export function createQuadrantClient( + options: QuadrantClientOptions, + nativeModule?: any, +): QuadrantClient; diff --git a/packages/quadrant-node/index.js b/packages/quadrant-node/index.js new file mode 100644 index 00000000..d9dd1d6c --- /dev/null +++ b/packages/quadrant-node/index.js @@ -0,0 +1,161 @@ +/** @format */ + +import { EventEmitter } from "node:events"; +import path from "node:path"; +import { fileURLToPath, pathToFileURL } from "node:url"; + +function normalizeNativeModule(module) { + if (!module) { + return module; + } + + if (typeof module.QuadrantHostAddon === "function") { + return module; + } + + if ( + module.default && + typeof module.default.QuadrantHostAddon === "function" + ) { + return module.default; + } + + return module; +} + +function resolveModuleSpecifier(specifier) { + if ( + specifier.startsWith("./") || + specifier.startsWith("../") || + specifier.startsWith("/") || + /^[A-Za-z]:[\\/]/.test(specifier) + ) { + return pathToFileURL(path.resolve(process.cwd(), specifier)).href; + } + + return specifier; +} + +async function loadNativeModule() { + if (process.env.QUADRANT_NAPI_MODULE) { + try { + return normalizeNativeModule( + await import(resolveModuleSpecifier(process.env.QUADRANT_NAPI_MODULE)), + ); + } catch (error) { + throw new Error( + `Failed to load Quadrant native module from ${process.env.QUADRANT_NAPI_MODULE}: ${error}`, + ); + } + } + + try { + return normalizeNativeModule(await import("./native/index.js")); + } catch (error) { + const packageDir = path.dirname(fileURLToPath(import.meta.url)); + throw new Error( + `Failed to load Quadrant native module from ${packageDir}: ${error}`, + ); + } +} + +const defaultNativeModule = await loadNativeModule(); + +function nativeMethod(target, ...names) { + for (const name of names) { + if (typeof target[name] === "function") { + return target[name].bind(target); + } + } + throw new Error(`Native method not found. Tried: ${names.join(", ")}`); +} + +function mapOptions(options) { + const mappedOptions = { + dataDir: options.dataDir, + oauthClientId: options.oauthClientId, + oauthClientSecret: options.oauthClientSecret, + quadrantApiKey: options.quadrantApiKey, + }; + + if (options.mcFolder != null) { + mappedOptions.mcFolder = options.mcFolder; + } + if (options.apiBaseUrl != null) { + mappedOptions.apiBaseUrl = options.apiBaseUrl; + } + if (options.configStoreName != null) { + mappedOptions.configStoreName = options.configStoreName; + } + if (options.updateStoreName != null) { + mappedOptions.updateStoreName = options.updateStoreName; + } + if (options.keyringServiceName != null) { + mappedOptions.keyringServiceName = options.keyringServiceName; + } + if (options.appVersion != null) { + mappedOptions.appVersion = options.appVersion; + } + if (options.osName != null) { + mappedOptions.osName = options.osName; + } + if (options.userAgent != null) { + mappedOptions.userAgent = options.userAgent; + } + + return mappedOptions; +} + +export function createQuadrantClient( + options, + nativeModule = defaultNativeModule, +) { + const host = new nativeModule.QuadrantHostAddon(mapOptions(options)); + const emitter = new EventEmitter(); + + nativeMethod( + host, + "onEvent", + "on_event", + )((rawEvent) => { + const event = + typeof rawEvent === "string" ? JSON.parse(rawEvent) : rawEvent; + emitter.emit("event", event); + emitter.emit(event.event, event.payload); + }); + + return { + startBackgroundWorkers: () => + nativeMethod( + host, + "startBackgroundWorkers", + "start_background_workers", + )(), + stopBackgroundWorkers: () => + nativeMethod(host, "stopBackgroundWorkers", "stop_background_workers")(), + shutdown: () => nativeMethod(host, "shutdown")(), + initConfig: () => nativeMethod(host, "initConfig", "init_config")(), + getMinecraftFolder: () => + nativeMethod(host, "getMinecraftFolder", "get_minecraft_folder")(), + invoke: (command, payload = null) => + nativeMethod(host, "invoke")(command, payload), + getModpacks: (hideFree = false) => + nativeMethod(host, "getModpacks", "get_modpacks")(hideFree), + getAccountInfo: () => + nativeMethod(host, "getAccountInfo", "get_account_info")(), + getNews: () => nativeMethod(host, "getNews", "get_news")(), + installMod: (args) => nativeMethod(host, "installMod", "install_mod")(args), + syncModpack: (modpack, overwrite = true) => + nativeMethod(host, "syncModpack", "sync_modpack")(modpack, overwrite), + on: (eventName, listener) => { + emitter.on(eventName, listener); + return () => emitter.off(eventName, listener); + }, + off: (eventName, listener) => { + emitter.off(eventName, listener); + }, + once: (eventName, listener) => { + emitter.once(eventName, listener); + }, + }; +} diff --git a/packages/quadrant-node/package.json b/packages/quadrant-node/package.json new file mode 100644 index 00000000..9f3a203f --- /dev/null +++ b/packages/quadrant-node/package.json @@ -0,0 +1,15 @@ +{ + "name": "@quadrant/quadrant-node", + "version": "26.4.1-preview.0", + "type": "module", + "main": "index.js", + "exports": { + ".": "./index.js" + }, + "types": "index.d.ts", + "files": [ + "index.js", + "index.d.ts", + "native/**/*" + ] +} diff --git a/postcss.config.cjs b/postcss.config.cjs index e5640725..483f3785 100644 --- a/postcss.config.cjs +++ b/postcss.config.cjs @@ -1,5 +1,5 @@ module.exports = { plugins: { - '@tailwindcss/postcss': {}, + "@tailwindcss/postcss": {}, }, }; diff --git a/scripts/build-electron.mjs b/scripts/build-electron.mjs new file mode 100644 index 00000000..4537bc88 --- /dev/null +++ b/scripts/build-electron.mjs @@ -0,0 +1,19 @@ +/** @format */ + +import path from "node:path"; +import { runCommand } from "./command-utils.mjs"; + +const rootDir = path.resolve(import.meta.dirname, ".."); +const extraArgs = process.argv.slice(2); + +function run(command, args) { + runCommand(command, args, { + cwd: rootDir, + stdio: "inherit", + shell: process.platform === "win32", + }); +} + +run("node", ["scripts/write-electron-runtime-config.mjs"]); +run("bun", ["run", "build"]); +run("node", ["scripts/build-napi.mjs", "--release", ...extraArgs]); diff --git a/scripts/build-napi.mjs b/scripts/build-napi.mjs new file mode 100644 index 00000000..e487523c --- /dev/null +++ b/scripts/build-napi.mjs @@ -0,0 +1,164 @@ +import { cpSync, existsSync, mkdirSync, readdirSync } from "node:fs"; +import path from "node:path"; +import { resolveCargoCommand, runCommand } from "./command-utils.mjs"; + +const rootDir = path.resolve(import.meta.dirname, ".."); +const srcTauriDir = path.join(rootDir, "src-tauri"); +const args = process.argv.slice(2); +const release = args.includes("--release"); +const profile = release ? "release" : "debug"; + +function getArgValue(flag) { + const exactMatch = args.find((arg) => arg.startsWith(`${flag}=`)); + if (exactMatch) { + return exactMatch.slice(flag.length + 1); + } + + const flagIndex = args.indexOf(flag); + if (flagIndex >= 0) { + return args[flagIndex + 1]; + } + + return undefined; +} + +function normalizePlatform(platform) { + if (!platform) { + return process.platform; + } + + switch (platform) { + case "windows": + return "win32"; + case "mac": + case "macos": + return "darwin"; + default: + return platform; + } +} + +function normalizeArch(arch) { + if (!arch) { + return process.arch; + } + + switch (arch) { + case "aarch64": + return "arm64"; + case "amd64": + return "x64"; + default: + return arch; + } +} + +function getRustTargetTriple(platform, arch) { + const key = `${platform}-${arch}`; + switch (key) { + case "win32-x64": + return "x86_64-pc-windows-msvc"; + case "win32-arm64": + return "aarch64-pc-windows-msvc"; + case "linux-x64": + return "x86_64-unknown-linux-gnu"; + case "linux-arm64": + return "aarch64-unknown-linux-gnu"; + case "darwin-x64": + return "x86_64-apple-darwin"; + case "darwin-arm64": + return "aarch64-apple-darwin"; + default: + throw new Error( + `Unsupported Electron native build target: platform=${platform} arch=${arch}`, + ); + } +} + +function findNativeBinary(directory) { + const entries = readdirSync(directory, { withFileTypes: true }); + for (const entry of entries) { + const fullPath = path.join(directory, entry.name); + if (entry.isDirectory()) { + const found = findNativeBinary(fullPath); + if (found) { + return found; + } + continue; + } + if ( + entry.isFile() && + entry.name.toLowerCase().includes("quadrant_napi") && + (entry.name.endsWith(".node") || + entry.name.endsWith(".dll") || + entry.name.endsWith(".so") || + entry.name.endsWith(".dylib")) + ) { + return fullPath; + } + } + return null; +} + +const targetPlatform = normalizePlatform( + getArgValue("--platform") ?? getArgValue("--os"), +); +const targetArch = normalizeArch(getArgValue("--arch")); +const rustTarget = + getArgValue("--target") ?? getRustTargetTriple(targetPlatform, targetArch); +const cargoCommand = resolveCargoCommand(); + +runCommand( + cargoCommand, + [ + "build", + "--manifest-path", + path.join("src-tauri", "Cargo.toml"), + "-p", + "quadrant-napi", + "--target", + rustTarget, + ...(release ? ["--release"] : []), + ], + { + cwd: rootDir, + stdio: "inherit", + shell: process.platform === "win32", + notFoundMessage: + "Rust is required to build the Electron native addon, but `cargo` was not found. Install Rust and ensure `cargo` is on PATH, or set `CARGO=/absolute/path/to/cargo` before running `bun run dev:electron`.", + }, +); + +const targetDir = path.join(srcTauriDir, "target", rustTarget, profile); +const builtNode = findNativeBinary(targetDir); + +if (!builtNode || !existsSync(builtNode)) { + throw new Error(`Could not find built N-API binary under ${targetDir}`); +} + +const nativeDir = path.join(rootDir, "packages", "quadrant-node", "native"); +mkdirSync(nativeDir, { recursive: true }); +cpSync( + path.join(srcTauriDir, "crates", "quadrant-napi", "index.js"), + path.join(nativeDir, "index.js"), +); +const targetNativeDir = path.join(nativeDir, `${targetPlatform}-${targetArch}`); +mkdirSync(targetNativeDir, { recursive: true }); +try { + cpSync(builtNode, path.join(targetNativeDir, "index.node")); + cpSync(builtNode, path.join(nativeDir, "index.node")); +} catch (error) { + if ( + error && + (error.code === "EIO" || error.code === "EPERM" || error.code === "EBUSY") + ) { + throw new Error( + `Built Quadrant N-API successfully, but could not update packages/quadrant-node/native/index.node because it is locked. Close Electron or any process using the addon, then rerun \`node scripts/build-napi.mjs\`. Original error: ${error.message}`, + ); + } + throw error; +} + +console.log( + `Copied ${builtNode} to ${path.join(targetNativeDir, "index.node")} and ${path.join(nativeDir, "index.node")}`, +); diff --git a/scripts/command-utils.mjs b/scripts/command-utils.mjs new file mode 100644 index 00000000..8ca22340 --- /dev/null +++ b/scripts/command-utils.mjs @@ -0,0 +1,50 @@ +import { existsSync } from "node:fs"; +import { homedir } from "node:os"; +import path from "node:path"; +import { spawnSync } from "node:child_process"; + +function formatCommand(command, args) { + return [command, ...args].join(" "); +} + +export function resolveCargoCommand() { + if (process.env.CARGO?.trim()) { + return process.env.CARGO.trim(); + } + + const rustupCargo = path.join( + homedir(), + ".cargo", + "bin", + process.platform === "win32" ? "cargo.exe" : "cargo", + ); + if (existsSync(rustupCargo)) { + return rustupCargo; + } + + return "cargo"; +} + +export function runCommand(command, args, options = {}) { + const { notFoundMessage, ...spawnOptions } = options; + const result = spawnSync(command, args, spawnOptions); + + if (result.error) { + if (result.error.code === "ENOENT") { + throw new Error( + notFoundMessage ?? + `Failed to run \`${formatCommand(command, args)}\`: command not found.`, + ); + } + + throw new Error( + `Failed to run \`${formatCommand(command, args)}\`: ${result.error.message}`, + ); + } + + if (result.status !== 0) { + process.exit(result.status ?? 1); + } + + return result; +} diff --git a/scripts/dev-electron.mjs b/scripts/dev-electron.mjs new file mode 100644 index 00000000..52709826 --- /dev/null +++ b/scripts/dev-electron.mjs @@ -0,0 +1,296 @@ +import { spawn } from "node:child_process"; +import http from "node:http"; +import path from "node:path"; +import chokidar from "chokidar"; + +const rootDir = path.resolve(import.meta.dirname, ".."); +const devUrl = "http://127.0.0.1:1420"; +const electronWatchGlobs = [ + "electron/**/*", + "package.json", + "packages/quadrant-node/**/*", + "scripts/build-napi.mjs", + "scripts/command-utils.mjs", + "scripts/write-electron-runtime-config.mjs", + "src-tauri/Cargo.toml", + "src-tauri/Cargo.lock", + "src-tauri/crates/quadrant-napi/**/*", +]; +const ignoredWatchGlobs = [ + "**/.DS_Store", + "**/node_modules/**", + "electron/runtime-config.generated.json", + "packages/quadrant-node/native/**", + "src-tauri/target/**", +]; + +let isShuttingDown = false; +let isRestartingElectron = false; +let electronRestartQueued = false; +let electronRestartTimer = null; +let electronProcess = null; + +function spawnProcess(command, args, extraEnv = {}) { + return spawn(command, args, { + cwd: rootDir, + stdio: "inherit", + shell: process.platform === "win32", + env: { + ...process.env, + ...extraEnv, + }, + }); +} + +function waitForChildProcess(childProcess, label) { + return new Promise((resolve, reject) => { + childProcess.on("error", (error) => { + reject(new Error(`Failed to start ${label}: ${error.message}`)); + }); + childProcess.on("exit", (code, signal) => { + if (code === 0) { + resolve(undefined); + return; + } + if (signal) { + reject(new Error(`${label} terminated with signal ${signal}`)); + return; + } + reject(new Error(`${label} exited with code ${code ?? 1}`)); + }); + }); +} + +async function waitForUrl(url, timeoutMs = 120000) { + const startedAt = Date.now(); + + while (Date.now() - startedAt < timeoutMs) { + const isReady = await new Promise((resolve) => { + const request = http.get(url, (response) => { + response.resume(); + resolve(response.statusCode !== undefined && response.statusCode < 500); + }); + request.on("error", () => resolve(false)); + request.setTimeout(2000, () => { + request.destroy(); + resolve(false); + }); + }); + + if (isReady) { + return; + } + + await new Promise((resolve) => setTimeout(resolve, 1000)); + } + + throw new Error(`Timed out waiting for ${url}`); +} + +async function runPrepareStep(command, args, label) { + const childProcess = spawnProcess(command, args); + await waitForChildProcess(childProcess, label); +} + +async function runElectronPrepareSteps() { + await runPrepareStep( + "node", + ["scripts/write-electron-runtime-config.mjs"], + "scripts/write-electron-runtime-config.mjs", + ); + await runPrepareStep( + "node", + ["scripts/build-napi.mjs"], + "scripts/build-napi.mjs", + ); +} + +function launchElectron() { + const nextElectronProcess = spawnProcess( + "bunx", + ["electron", "electron/main.mjs"], + { + QUADRANT_ELECTRON_DEV_SERVER_URL: devUrl, + }, + ); + + electronProcess = nextElectronProcess; + nextElectronProcess.on("exit", (code, signal) => { + if (electronProcess !== nextElectronProcess) { + return; + } + + electronProcess = null; + + if (isShuttingDown || isRestartingElectron) { + return; + } + + if (signal) { + console.error(`Electron exited with signal ${signal}`); + process.exit(1); + return; + } + + process.exit(code ?? 0); + }); +} + +async function stopElectronProcess() { + if (!electronProcess || electronProcess.exitCode !== null) { + electronProcess = null; + return; + } + + const runningElectronProcess = electronProcess; + + await new Promise((resolve) => { + let resolved = false; + + const finalize = () => { + if (resolved) { + return; + } + resolved = true; + clearTimeout(forceKillTimeout); + resolve(undefined); + }; + + const forceKillTimeout = setTimeout(() => { + if (!runningElectronProcess.killed) { + runningElectronProcess.kill("SIGKILL"); + } + }, 10000); + + runningElectronProcess.once("exit", finalize); + runningElectronProcess.kill("SIGTERM"); + }); + + if (electronProcess === runningElectronProcess) { + electronProcess = null; + } +} + +async function restartElectron(reason) { + if (isShuttingDown) { + return; + } + + if (isRestartingElectron) { + electronRestartQueued = true; + return; + } + + isRestartingElectron = true; + + do { + electronRestartQueued = false; + console.log(`[dev:electron] Restarting Electron (${reason})`); + + try { + await stopElectronProcess(); + await runElectronPrepareSteps(); + if (!isShuttingDown) { + launchElectron(); + } + } catch (error) { + console.error( + `[dev:electron] Failed to restart Electron: ${error.message}`, + ); + } + } while (electronRestartQueued && !isShuttingDown); + + isRestartingElectron = false; +} + +function scheduleElectronRestart(reason) { + if (isShuttingDown) { + return; + } + + if (electronRestartTimer) { + clearTimeout(electronRestartTimer); + } + + electronRestartTimer = setTimeout(() => { + electronRestartTimer = null; + restartElectron(reason).catch((error) => { + console.error( + `[dev:electron] Unexpected restart failure: ${error.message}`, + ); + }); + }, 250); +} + +const viteProcess = spawnProcess("bun", [ + "run", + "dev", + "--", + "--host", + "127.0.0.1", +]); + +const electronWatcher = chokidar.watch(electronWatchGlobs, { + cwd: rootDir, + ignoreInitial: true, + ignored: ignoredWatchGlobs, +}); + +electronWatcher.on("all", (eventName, filePath) => { + scheduleElectronRestart(`${eventName} ${filePath}`); +}); + +const cleanup = async () => { + if (isShuttingDown) { + return; + } + + isShuttingDown = true; + + if (electronRestartTimer) { + clearTimeout(electronRestartTimer); + electronRestartTimer = null; + } + + await Promise.allSettled([ + electronWatcher.close(), + stopElectronProcess(), + ]); + + if (viteProcess.exitCode === null) { + viteProcess.kill("SIGTERM"); + } +}; + +process.on("SIGINT", () => { + cleanup().finally(() => process.exit(0)); +}); + +process.on("SIGTERM", () => { + cleanup().finally(() => process.exit(0)); +}); + +viteProcess.on("exit", async (code, signal) => { + if (isShuttingDown) { + return; + } + + await cleanup(); + + if (signal) { + console.error(`Vite dev server terminated with signal ${signal}`); + process.exit(1); + return; + } + + process.exit(code ?? 0); +}); + +try { + await runElectronPrepareSteps(); + await waitForUrl(devUrl); + launchElectron(); +} catch (error) { + await cleanup(); + throw error; +} diff --git a/scripts/package-electron.mjs b/scripts/package-electron.mjs new file mode 100644 index 00000000..df2a4c4d --- /dev/null +++ b/scripts/package-electron.mjs @@ -0,0 +1,53 @@ +import path from "node:path"; +import { runCommand } from "./command-utils.mjs"; + +const rootDir = path.resolve(import.meta.dirname, ".."); +const extraArgs = process.argv.slice(2); + +function getArgValue(flag) { + const exactMatch = extraArgs.find((arg) => arg.startsWith(`${flag}=`)); + if (exactMatch) { + return exactMatch.slice(flag.length + 1); + } + + const flagIndex = extraArgs.indexOf(flag); + if (flagIndex >= 0) { + return extraArgs[flagIndex + 1]; + } + + return undefined; +} + +function normalizeArch(arch) { + if (!arch) { + return process.arch; + } + + switch (arch) { + case "aarch64": + return "arm64"; + case "amd64": + return "x64"; + default: + return arch; + } +} + +function run(command, args) { + runCommand(command, args, { + cwd: rootDir, + stdio: "inherit", + shell: process.platform === "win32", + }); +} + +const targetArch = normalizeArch(getArgValue("--arch")); +const electronBuilderArchFlag = `--${targetArch}`; + +run("node", ["scripts/build-electron.mjs", ...extraArgs]); +run("bunx", [ + "electron-builder", + "--config", + "electron-builder.json", + electronBuilderArchFlag, +]); diff --git a/scripts/tsconfig.json b/scripts/tsconfig.json new file mode 100644 index 00000000..1a96a3a8 --- /dev/null +++ b/scripts/tsconfig.json @@ -0,0 +1,12 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "NodeNext", + "moduleResolution": "NodeNext", + "allowJs": true, + "checkJs": false, + "types": ["node"], + "skipLibCheck": true + }, + "include": ["./*.mjs"] +} diff --git a/scripts/update_flathub_manifest.py b/scripts/update_flathub_manifest.py index cdc28006..07c561d3 100644 --- a/scripts/update_flathub_manifest.py +++ b/scripts/update_flathub_manifest.py @@ -62,11 +62,11 @@ def main() -> int: ) amd64_url = ( f"https://github.com/{args.release_repo}/releases/download/{args.tag}/" - f"Quadrant_{args.version}_amd64.deb" + f"Quadrant-{args.version}-linux-x64-electron.AppImage" ) arm64_url = ( f"https://github.com/{args.release_repo}/releases/download/{args.tag}/" - f"Quadrant_{args.version}_arm64.deb" + f"Quadrant-{args.version}-linux-arm64-electron.AppImage" ) metainfo_pattern = re.compile( @@ -78,14 +78,14 @@ def main() -> int: amd64_pattern = re.compile( r"(?m)^(\s*-\s+type:\s+file\r?\n" r"\s*url:\s+)https://github\.com/[^/\r\n]+/[^/\r\n]+/releases/download/" - r"[^/\r\n]+/Quadrant_[^/\r\n]+_amd64\.deb" + r"[^/\r\n]+/(?:Quadrant_[^/\r\n]+_amd64\.deb|Quadrant-[^/\r\n]+-linux-x64-electron\.AppImage)" r"(\r?\n\s*sha256:\s+)[0-9a-f]{64}" r"(\r?\n\s*only-arches:\s*\[x86_64\]\r?\n)" ) arm64_pattern = re.compile( r"(?m)^(\s*-\s+type:\s+file\r?\n" r"\s*url:\s+)https://github\.com/[^/\r\n]+/[^/\r\n]+/releases/download/" - r"[^/\r\n]+/Quadrant_[^/\r\n]+_arm64\.deb" + r"[^/\r\n]+/(?:Quadrant_[^/\r\n]+_arm64\.deb|Quadrant-[^/\r\n]+-linux-arm64-electron\.AppImage)" r"(\r?\n\s*sha256:\s+)[0-9a-f]{64}" r"(\r?\n\s*only-arches:\s*\[aarch64\]\r?\n)" ) diff --git a/scripts/write-electron-runtime-config.mjs b/scripts/write-electron-runtime-config.mjs new file mode 100644 index 00000000..71daabf8 --- /dev/null +++ b/scripts/write-electron-runtime-config.mjs @@ -0,0 +1,42 @@ +import { mkdirSync, writeFileSync } from "node:fs"; +import path from "node:path"; + +const rootDir = path.resolve(import.meta.dirname, ".."); +const outputPath = path.join( + rootDir, + "electron", + "runtime-config.generated.json", +); + +function normalizeOptionalConfigValue(value) { + if (typeof value !== "string") { + return null; + } + + const trimmed = value.trim(); + return trimmed.length > 0 ? trimmed : null; +} + +function normalizeRequiredConfigValue(value) { + if (typeof value !== "string") { + return ""; + } + + return value.trim(); +} + +const config = { + oauthClientId: normalizeRequiredConfigValue( + process.env.QUADRANT_OAUTH2_CLIENT_ID, + ), + oauthClientSecret: normalizeRequiredConfigValue( + process.env.QUADRANT_OAUTH2_CLIENT_SECRET, + ), + quadrantApiKey: normalizeRequiredConfigValue(process.env.QUADRANT_API_KEY), + apiBaseUrl: normalizeOptionalConfigValue(process.env.QUADRANT_API_BASE_URL), +}; + +mkdirSync(path.dirname(outputPath), { recursive: true }); +writeFileSync(outputPath, JSON.stringify(config, null, 2)); + +console.log(`Wrote Electron runtime config to ${outputPath}`); diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index 1eb024f1..010d1fd8 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -52,21 +52,6 @@ dependencies = [ "libc", ] -[[package]] -name = "anstream" -version = "0.6.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43d5b281e737544384e969a5ccad3f1cdd24b48086a0fc1b2a5262a26b8f4f4a" -dependencies = [ - "anstyle", - "anstyle-parse 0.2.7", - "anstyle-query", - "anstyle-wincon", - "colorchoice", - "is_terminal_polyfill", - "utf8parse", -] - [[package]] name = "anstream" version = "1.0.0" @@ -74,7 +59,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "824a212faf96e9acacdbd09febd34438f8f711fb84e09a8916013cd7815ca28d" dependencies = [ "anstyle", - "anstyle-parse 1.0.0", + "anstyle-parse", "anstyle-query", "anstyle-wincon", "colorchoice", @@ -88,15 +73,6 @@ version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000" -[[package]] -name = "anstyle-parse" -version = "0.2.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2" -dependencies = [ - "utf8parse", -] - [[package]] name = "anstyle-parse" version = "1.0.0" @@ -384,9 +360,9 @@ dependencies = [ [[package]] name = "aws-lc-sys" -version = "0.39.0" +version = "0.39.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fa7e52a4c5c547c741610a2c6f123f3881e409b714cd27e6798ef020c514f0a" +checksum = "83a25cf98105baa966497416dbd42565ce3a8cf8dbfd59803ec9ad46f3126399" dependencies = [ "cc", "cmake", @@ -617,9 +593,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.57" +version = "1.2.59" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a0dd1ca384932ff3641c8718a02769f1698e7563dc6974ffd03346116310423" +checksum = "b7a4d3ec6524d28a329fc53654bbadc9bdd7b0431f5d65f1a56ffb28a1ee5283" dependencies = [ "find-msvc-tools", "jobserver", @@ -705,7 +681,7 @@ version = "4.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "714a53001bf66416adb0e2ef5ac857140e7dc3a0c48fb28b2f10762fc4b5069f" dependencies = [ - "anstream 1.0.0", + "anstream", "anstyle", "clap_lex", "strsim", @@ -728,9 +704,9 @@ dependencies = [ [[package]] name = "cmake" -version = "0.1.57" +version = "0.1.58" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75443c44cd6b379beb8c5b45d85d0773baf31cce901fe7bb252f4eff3008ef7d" +checksum = "c0f78a02292a74a88ac736019ab962ece0bc380e3f977bf72e376c5d78ff0678" dependencies = [ "cc", ] @@ -828,32 +804,23 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" [[package]] -name = "cookie" -version = "0.18.1" +name = "convert_case" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ddef33a339a91ea89fb53151bd0a4689cfce27055c291dfa69945475d22c747" +checksum = "affbf0190ed2caf063e3def54ff444b449371d55c58e513a95ab98eca50adb49" dependencies = [ - "percent-encoding", - "time", - "version_check", + "unicode-segmentation", ] [[package]] -name = "cookie_store" -version = "0.21.1" +name = "cookie" +version = "0.18.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2eac901828f88a5241ee0600950ab981148a18f2f756900ffba1b125ca6a3ef9" +checksum = "4ddef33a339a91ea89fb53151bd0a4689cfce27055c291dfa69945475d22c747" dependencies = [ - "cookie", - "document-features", - "idna", - "log", - "publicsuffix", - "serde", - "serde_derive", - "serde_json", + "percent-encoding", "time", - "url", + "version_check", ] [[package]] @@ -1050,6 +1017,22 @@ dependencies = [ "syn 2.0.117", ] +[[package]] +name = "ctor" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "352d39c2f7bef1d6ad73db6f5160efcaed66d94ef8c6c573a8410c00bf909a98" +dependencies = [ + "ctor-proc-macro", + "dtor", +] + +[[package]] +name = "ctor-proc-macro" +version = "0.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52560adf09603e58c9a7ee1fe1dcb95a16927b17c127f0ac02d6e768a0e25bc1" + [[package]] name = "darling" version = "0.20.11" @@ -1154,9 +1137,9 @@ dependencies = [ [[package]] name = "deflate64" -version = "0.1.11" +version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "807800ff3288b621186fe0a8f3392c4652068257302709c24efd918c3dffcdc2" +checksum = "ac6b926516df9c60bfa16e107b21086399f8285a44ca9711344b9e553c5146e2" [[package]] name = "deranged" @@ -1216,7 +1199,7 @@ version = "0.99.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6edb4b64a43d977b8e99788fe3a04d483834fba1215a7e02caa415b626497f7f" dependencies = [ - "convert_case", + "convert_case 0.4.0", "proc-macro2", "quote", "rustc_version", @@ -1425,6 +1408,21 @@ dependencies = [ "dtoa", ] +[[package]] +name = "dtor" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1057d6c64987086ff8ed0fd3fbf377a6b7d205cc7715868cd401705f715cbe4" +dependencies = [ + "dtor-proc-macro", +] + +[[package]] +name = "dtor-proc-macro" +version = "0.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f678cf4a922c215c63e0de95eb1ff08a958a81d47e485cf9da1e27bf6305cfa5" + [[package]] name = "dunce" version = "1.0.5" @@ -1439,9 +1437,9 @@ checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555" [[package]] name = "embed-resource" -version = "3.0.7" +version = "3.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47ec73ddcf6b7f23173d5c3c5a32b5507dc0a734de7730aa14abc5d5e296bb5f" +checksum = "63a1d0de4f2249aa0ff5884d7080814f446bb241a559af6c170a41e878ed2d45" dependencies = [ "cc", "memchr", @@ -1507,9 +1505,9 @@ dependencies = [ [[package]] name = "env_filter" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a1c3cc8e57274ec99de65301228b537f1e4eedc1b8e0f9411c6caac8ae7308f" +checksum = "32e90c2accc4b07a8456ea0debdc2e7587bdd890680d71173a15d4ae604f6eef" dependencies = [ "log", "regex", @@ -1517,11 +1515,11 @@ dependencies = [ [[package]] name = "env_logger" -version = "0.11.9" +version = "0.11.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2daee4ea451f429a58296525ddf28b45a3b64f1acf6587e2067437bb11e218d" +checksum = "0621c04f2196ac3f488dd583365b9c09be011a4ab8b9f37248ffcc8f6198b56a" dependencies = [ - "anstream 0.6.21", + "anstream", "anstyle", "env_filter", "jiff", @@ -1584,9 +1582,9 @@ dependencies = [ [[package]] name = "fastrand" -version = "2.3.0" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" +checksum = "a043dc74da1e37d6afe657061213aa6f425f855399a11d3463c6ecccc4dfda1f" [[package]] name = "fax" @@ -2197,7 +2195,7 @@ dependencies = [ "futures-core", "futures-sink", "http", - "indexmap 2.13.0", + "indexmap 2.13.1", "slab", "tokio", "tokio-util", @@ -2466,18 +2464,18 @@ dependencies = [ [[package]] name = "hybrid-array" -version = "0.4.8" +version = "0.4.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8655f91cd07f2b9d0c24137bd650fe69617773435ee5ec83022377777ce65ef1" +checksum = "3944cf8cf766b40e2a1a333ee5e9b563f854d5fa49d6a8ca2764e97c6eddb214" dependencies = [ "typenum", ] [[package]] name = "hyper" -version = "1.8.1" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ab2d4f250c3d7b1c9fcdff1cece94ea4e2dfbec68614f7b87cb205f24ca9d11" +checksum = "6299f016b246a94207e63da54dbe807655bf9e00044f73ded42c3ac5305fbcca" dependencies = [ "atomic-waker", "bytes", @@ -2490,7 +2488,6 @@ dependencies = [ "httpdate", "itoa", "pin-project-lite", - "pin-utils", "smallvec", "tokio", "want", @@ -2574,12 +2571,13 @@ dependencies = [ [[package]] name = "icu_collections" -version = "2.1.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c6b649701667bbe825c3b7e6388cb521c23d88644678e83c0c4d0a621a34b43" +checksum = "2984d1cd16c883d7935b9e07e44071dca8d917fd52ecc02c04d5fa0b5a3f191c" dependencies = [ "displaydoc", "potential_utf", + "utf8_iter", "yoke", "zerofrom", "zerovec", @@ -2587,9 +2585,9 @@ dependencies = [ [[package]] name = "icu_locale_core" -version = "2.1.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edba7861004dd3714265b4db54a3c390e880ab658fec5f7db895fae2046b5bb6" +checksum = "92219b62b3e2b4d88ac5119f8904c10f8f61bf7e95b640d25ba3075e6cac2c29" dependencies = [ "displaydoc", "litemap", @@ -2600,9 +2598,9 @@ dependencies = [ [[package]] name = "icu_normalizer" -version = "2.1.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f6c8828b67bf8908d82127b2054ea1b4427ff0230ee9141c54251934ab1b599" +checksum = "c56e5ee99d6e3d33bd91c5d85458b6005a22140021cc324cea84dd0e72cff3b4" dependencies = [ "icu_collections", "icu_normalizer_data", @@ -2614,15 +2612,15 @@ dependencies = [ [[package]] name = "icu_normalizer_data" -version = "2.1.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7aedcccd01fc5fe81e6b489c15b247b8b0690feb23304303a9e560f37efc560a" +checksum = "da3be0ae77ea334f4da67c12f149704f19f81d1adf7c51cf482943e84a2bad38" [[package]] name = "icu_properties" -version = "2.1.2" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "020bfc02fe870ec3a66d93e677ccca0562506e5872c650f893269e08615d74ec" +checksum = "bee3b67d0ea5c2cca5003417989af8996f8604e34fb9ddf96208a033901e70de" dependencies = [ "icu_collections", "icu_locale_core", @@ -2634,15 +2632,15 @@ dependencies = [ [[package]] name = "icu_properties_data" -version = "2.1.2" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "616c294cf8d725c6afcd8f55abc17c56464ef6211f9ed59cccffe534129c77af" +checksum = "8e2bbb201e0c04f7b4b3e14382af113e17ba4f63e2c9d2ee626b720cbce54a14" [[package]] name = "icu_provider" -version = "2.1.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85962cf0ce02e1e0a629cc34e7ca3e373ce20dda4c4d7294bbd0bf1fdb59e614" +checksum = "139c4cf31c8b5f33d7e199446eff9c1e02decfc2f0eec2c8d71f65befa45b421" dependencies = [ "displaydoc", "icu_locale_core", @@ -2713,9 +2711,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.13.0" +version = "2.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017" +checksum = "45a8a2b9cb3e0b0c1803dbb0758ffac5de2f425b23c28f518faabd9d805342ff" dependencies = [ "equivalent", "hashbrown 0.16.1", @@ -2769,9 +2767,9 @@ checksum = "d98f6fed1fde3f8c21bc40a1abb88dd75e67924f9cffc3ef95607bad8017f8e2" [[package]] name = "iri-string" -version = "0.7.10" +version = "0.7.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c91338f0783edbd6195decb37bae672fd3b165faffb89bf7b9e6942f8b1a731a" +checksum = "25e659a4bb38e810ebc252e53b5814ff908a8c58c2a9ce2fae1bbec24cbf4e20" dependencies = [ "memchr", "serde", @@ -2864,7 +2862,7 @@ dependencies = [ "cesu8", "cfg-if", "combine", - "jni-sys", + "jni-sys 0.3.1", "log", "thiserror 1.0.69", "walkdir", @@ -2873,9 +2871,31 @@ dependencies = [ [[package]] name = "jni-sys" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" +checksum = "41a652e1f9b6e0275df1f15b32661cf0d4b78d4d87ddec5e0c3c20f097433258" +dependencies = [ + "jni-sys 0.4.1", +] + +[[package]] +name = "jni-sys" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6377a88cb3910bee9b0fa88d4f42e1d2da8e79915598f65fb0c7ee14c878af2" +dependencies = [ + "jni-sys-macros", +] + +[[package]] +name = "jni-sys-macros" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38c0b942f458fe50cdac086d2f946512305e5631e720728f2a61aabcd47a6264" +dependencies = [ + "quote", + "syn 2.0.117", +] [[package]] name = "jobserver" @@ -2889,10 +2909,12 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.91" +version = "0.3.94" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b49715b7073f385ba4bc528e5747d02e66cb39c6146efb66b781f131f0fb399c" +checksum = "2e04e2ef80ce82e13552136fabeef8a5ed1f985a96805761cbb9a2c34e7664d9" dependencies = [ + "cfg-if", + "futures-util", "once_cell", "wasm-bindgen", ] @@ -2984,7 +3006,7 @@ checksum = "02cb977175687f33fa4afa0c95c112b987ea1443e5a51c8f8ff27dc618270cc2" dependencies = [ "cssparser 0.29.6", "html5ever 0.29.1", - "indexmap 2.13.0", + "indexmap 2.13.1", "selectors 0.24.0", ] @@ -3014,7 +3036,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6e9ec52138abedcc58dc17a7c6c0c00a2bdb4f3427c7f63fa97fd0d859155caf" dependencies = [ "gtk-sys", - "libloading", + "libloading 0.7.4", "once_cell", ] @@ -3026,9 +3048,9 @@ checksum = "2c4a545a15244c7d945065b5d392b2d2d7f21526fba56ce51467b06ed445e8f7" [[package]] name = "libc" -version = "0.2.183" +version = "0.2.184" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5b646652bf6661599e1da8901b3b9522896f01e736bad5f723fe7a3a27f899d" +checksum = "48f5d2a454e16a5ea0f4ced81bd44e4cfc7bd3a507b61887c99fd3538b28e4af" [[package]] name = "libdbus-sys" @@ -3049,11 +3071,21 @@ dependencies = [ "winapi", ] +[[package]] +name = "libloading" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "754ca22de805bb5744484a5b151a9e1a8e837d5dc232c2d7d8c2e3492edc8b60" +dependencies = [ + "cfg-if", + "windows-link 0.2.1", +] + [[package]] name = "libredox" -version = "0.1.14" +version = "0.1.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1744e39d1d6a9948f4f388969627434e31128196de472883b39f148769bfe30a" +checksum = "7ddbf48fd451246b1f8c2610bd3b4ac0cc6e149d89832867093ab69a17194f08" dependencies = [ "bitflags 2.11.0", "libc", @@ -3069,9 +3101,9 @@ checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" [[package]] name = "litemap" -version = "0.8.1" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6373607a59f0be73a39b6fe456b8192fcc3585f602af20751600e974dd455e77" +checksum = "92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0" [[package]] name = "litrs" @@ -3208,9 +3240,9 @@ dependencies = [ [[package]] name = "mio" -version = "1.1.1" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a69bcab0ad47271a0234d9422b131806bf3968021e5dc9328caf2d4cd58557fc" +checksum = "50b7e5b27aa02a74bac8c3f23f448f8d87ff11f92d3aac1a6ed369ee08cc56c1" dependencies = [ "libc", "log", @@ -3220,9 +3252,9 @@ dependencies = [ [[package]] name = "moka" -version = "0.12.14" +version = "0.12.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85f8024e1c8e71c778968af91d43700ce1d11b219d127d79fb2934153b82b42b" +checksum = "957228ad12042ee839f93c8f257b62b4c0ab5eaae1d4fa60de53b27c9d7c5046" dependencies = [ "async-lock", "crossbeam-channel", @@ -3250,9 +3282,9 @@ dependencies = [ [[package]] name = "muda" -version = "0.17.1" +version = "0.17.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01c1738382f66ed56b3b9c8119e794a2e23148ac8ea214eda86622d4cb9d415a" +checksum = "7c9fec5a4e89860383d778d10563a605838f8f0b2f9303868937e5ff32e86177" dependencies = [ "crossbeam-channel", "dpi", @@ -3269,6 +3301,66 @@ dependencies = [ "windows-sys 0.60.2", ] +[[package]] +name = "napi" +version = "3.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb7848c221fb7bb789e02f01875287ebb1e078b92a6566a34de01ef8806e7c2b" +dependencies = [ + "bitflags 2.11.0", + "ctor 0.8.0", + "futures", + "napi-build", + "napi-sys", + "nohash-hasher", + "rustc-hash", + "serde", + "serde_json", + "tokio", +] + +[[package]] +name = "napi-build" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d376940fd5b723c6893cd1ee3f33abbfd86acb1cd1ec079f3ab04a2a3bc4d3b1" + +[[package]] +name = "napi-derive" +version = "3.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60867ff9a6f76e82350e0c3420cb0736f5866091b61d7d8a024baa54b0ec17dd" +dependencies = [ + "convert_case 0.11.0", + "ctor 0.8.0", + "napi-derive-backend", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "napi-derive-backend" +version = "5.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0864cf6a82e2cfb69067374b64c9253d7e910e5b34db833ed7495dda56ccb18" +dependencies = [ + "convert_case 0.11.0", + "proc-macro2", + "quote", + "semver", + "syn 2.0.117", +] + +[[package]] +name = "napi-sys" +version = "3.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8eb602b84d7c1edae45e50bbf1374696548f36ae179dfa667f577e384bb90c2b" +dependencies = [ + "libloading 0.9.0", +] + [[package]] name = "ndk" version = "0.9.0" @@ -3276,7 +3368,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3f42e7bbe13d351b6bead8286a43aac9534b82bd3cc43e47037f012ebfd62d4" dependencies = [ "bitflags 2.11.0", - "jni-sys", + "jni-sys 0.3.1", "log", "ndk-sys", "num_enum", @@ -3296,7 +3388,7 @@ version = "0.6.0+11769913" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ee6cda3051665f1fb8d9e08fc35c96d5a244fb1be711a03b71118828afc9a873" dependencies = [ - "jni-sys", + "jni-sys 0.3.1", ] [[package]] @@ -3329,6 +3421,12 @@ version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72ef4a56884ca558e5ddb05a1d1e7e1bfd9a68d9ed024c21704cc98872dae1bb" +[[package]] +name = "nohash-hasher" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bf50223579dc7cdcfb3bfcacf7069ff68243f8c363f62ffa99cf000a6b9c451" + [[package]] name = "nom" version = "8.0.0" @@ -3371,9 +3469,9 @@ dependencies = [ [[package]] name = "notify-rust" -version = "4.12.0" +version = "4.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21af20a1b50be5ac5861f74af1a863da53a11c38684d9818d82f1c42f7fdc6c2" +checksum = "9612133a804b4bc753f9f806de73fc9730b7694eb1bada2dc252cce022638be8" dependencies = [ "futures-lite", "log", @@ -3395,9 +3493,9 @@ dependencies = [ [[package]] name = "num-conv" -version = "0.2.0" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf97ec579c3c42f953ef76dbf8d55ac91fb219dde70e49aa4a6b7d74e9919050" +checksum = "c6673768db2d862beb9b39a78fdcb1a69439615d5794a1be50caa9bc92c81967" [[package]] name = "num-traits" @@ -3871,7 +3969,7 @@ checksum = "8701b58ea97060d5e5b155d383a69952a60943f0e6dfe30b04c287beb0b27455" dependencies = [ "fixedbitset", "hashbrown 0.15.5", - "indexmap 2.13.0", + "indexmap 2.13.1", ] [[package]] @@ -4067,12 +4165,6 @@ version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" -[[package]] -name = "pin-utils" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" - [[package]] name = "piper" version = "0.2.5" @@ -4103,7 +4195,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "740ebea15c5d1428f910cd1a5f52cebf8d25006245ed8ade92702f4943d91e07" dependencies = [ "base64 0.22.1", - "indexmap 2.13.0", + "indexmap 2.13.1", "quick-xml 0.38.4", "serde", "time", @@ -4178,9 +4270,9 @@ dependencies = [ [[package]] name = "potential_utf" -version = "0.1.4" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b73949432f5e2a09657003c25bca5e19a0e9c84f8058ca374f49e0ebe605af77" +checksum = "0103b1cef7ec0cf76490e969665504990193874ea05c85ff9bab8b911d0a0564" dependencies = [ "zerovec", ] @@ -4248,7 +4340,7 @@ version = "3.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e67ba7e9b2b56446f1d419b1d807906278ffa1a658a8a5d8a39dcb1f5a78614f" dependencies = [ - "toml_edit 0.25.5+spec-1.1.0", + "toml_edit 0.25.10+spec-1.1.0", ] [[package]] @@ -4314,7 +4406,7 @@ checksum = "b5a041e753da8b807c9255f28de81879c78c876392ff2469cde94799b2896b9d" [[package]] name = "quadrant-core" -version = "26.4.0-stable" +version = "26.4.1-preview.0" dependencies = [ "anyhow", "chrono", @@ -4336,12 +4428,43 @@ dependencies = [ "tokio", "urlencoding", "uuid", - "zip 8.4.0", + "zip 8.5.0", +] + +[[package]] +name = "quadrant-host" +version = "26.4.1-preview.0" +dependencies = [ + "anyhow", + "colog", + "futures", + "keyring", + "log", + "quadrant-core", + "serde", + "serde_json", + "tempfile", + "tokio", + "tokio-tungstenite", + "url", + "uuid", +] + +[[package]] +name = "quadrant-napi" +version = "26.4.1-preview.0" +dependencies = [ + "napi", + "napi-build", + "napi-derive", + "quadrant-host", + "serde_json", + "tokio", ] [[package]] name = "quadrant_next" -version = "26.4.0-stable" +version = "26.4.1-preview.0" dependencies = [ "anyhow", "chrono", @@ -4355,6 +4478,7 @@ dependencies = [ "once_cell", "open", "quadrant-core", + "quadrant-host", "reqwest 0.13.2", "reqwest-middleware", "rss", @@ -4383,7 +4507,7 @@ dependencies = [ "url", "urlencoding", "uuid", - "zip 8.4.0", + "zip 8.5.0", ] [[package]] @@ -4711,7 +4835,7 @@ dependencies = [ "base64 0.22.1", "bytes", "cookie", - "cookie_store 0.22.1", + "cookie_store", "encoding_rs", "futures-core", "h2", @@ -4754,7 +4878,7 @@ dependencies = [ "base64 0.22.1", "bytes", "cookie", - "cookie_store 0.22.1", + "cookie_store", "encoding_rs", "futures-core", "futures-util", @@ -4867,9 +4991,9 @@ dependencies = [ [[package]] name = "rustc-hash" -version = "2.1.1" +version = "2.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" +checksum = "94300abf3f1ae2e2b8ffb7b58043de3d399c73fa6f4b73826402a5c457614dbe" [[package]] name = "rustc_version" @@ -5131,9 +5255,9 @@ dependencies = [ [[package]] name = "semver" -version = "1.0.27" +version = "1.0.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" +checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" dependencies = [ "serde", "serde_core", @@ -5237,9 +5361,9 @@ dependencies = [ [[package]] name = "serde_spanned" -version = "1.0.4" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8bbf91e5a4d6315eee45e704372590b30e260ee83af6639d64557f51b067776" +checksum = "6662b5879511e06e8999a8a235d848113e942c9124f211511b16466ee2995f26" dependencies = [ "serde_core", ] @@ -5266,7 +5390,7 @@ dependencies = [ "chrono", "hex", "indexmap 1.9.3", - "indexmap 2.13.0", + "indexmap 2.13.1", "schemars 0.9.0", "schemars 1.2.1", "serde_core", @@ -5379,9 +5503,9 @@ dependencies = [ [[package]] name = "simd-adler32" -version = "0.3.8" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e320a6c5ad31d271ad523dcf3ad13e2767ad8b1cb8f047f75a8aeaf8da139da2" +checksum = "703d5c7ef118737c72f1af64ad2f6f8c5e1921f818cdcb97b8fe6fc69bf66214" [[package]] name = "similar" @@ -5657,9 +5781,9 @@ checksum = "7b2093cf4c8eb1e67749a6762251bc9cd836b6fc171623bd0a9d324d37af2417" [[package]] name = "tao" -version = "0.34.6" +version = "0.34.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e06d52c379e63da659a483a958110bbde891695a0ecb53e48cc7786d5eda7bb" +checksum = "9103edf55f2da3c82aea4c7fab7c4241032bfeea0e71fa557d98e00e7ce7cc20" dependencies = [ "bitflags 2.11.0", "block2", @@ -5898,9 +6022,9 @@ dependencies = [ [[package]] name = "tauri-plugin-deep-link" -version = "2.4.7" +version = "2.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94deb2e2e4641514ac496db2cddcfc850d6fc9d51ea17b82292a0490bd20ba5b" +checksum = "3db49816aee496a9b200d55b55ab6ae73fd50847c79f2fabc7ee20871fa75c95" dependencies = [ "dunce", "plist", @@ -5919,9 +6043,9 @@ dependencies = [ [[package]] name = "tauri-plugin-dialog" -version = "2.6.0" +version = "2.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9204b425d9be8d12aa60c2a83a289cf7d1caae40f57f336ed1155b3a5c0e359b" +checksum = "a1fa4150c95ae391946cc8b8f905ab14797427caba3a8a2f79628e956da91809" dependencies = [ "log", "raw-window-handle", @@ -5937,15 +6061,17 @@ dependencies = [ [[package]] name = "tauri-plugin-fs" -version = "2.4.5" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed390cc669f937afeb8b28032ce837bac8ea023d975a2e207375ec05afaf1804" +checksum = "36e1ec28b79f3d0683f4507e1615c36292c0ea6716668770d4396b9b39871ed8" dependencies = [ "anyhow", "dunce", "glob", + "log", "notify", "notify-debouncer-full", + "objc2-foundation", "percent-encoding", "schemars 0.8.22", "serde", @@ -5961,12 +6087,12 @@ dependencies = [ [[package]] name = "tauri-plugin-http" -version = "2.5.7" +version = "2.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8f069451c4e87e7e2636b7f065a4c52866c4ce5e60e2d53fa1038edb6d184dc" +checksum = "cfba7d4ec72763f9d1fdf73c217747f01e2c84b08b87a8cacd2f94f35853f84d" dependencies = [ "bytes", - "cookie_store 0.21.1", + "cookie_store", "data-url", "http", "regex", @@ -6059,9 +6185,9 @@ dependencies = [ [[package]] name = "tauri-plugin-persisted-scope" -version = "2.3.5" +version = "2.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcb33765b205abc72a1384d0a73489a00bae6ca1e151c5e824ac80115949d2e1" +checksum = "e54679b72e4977ddbd16ceea4a1eb05f764ce3582667c4253352c2d61e7298df" dependencies = [ "aho-corasick", "bincode", @@ -6075,9 +6201,9 @@ dependencies = [ [[package]] name = "tauri-plugin-single-instance" -version = "2.4.0" +version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc61e4822b8f74d68278e09161d3e3fdd1b14b9eb781e24edccaabf10c420e8c" +checksum = "a33a5b7d78f0dec4406b003ea87c40bf928d801b6fd9323a556172c91d8712c1" dependencies = [ "serde", "serde_json", @@ -6107,9 +6233,9 @@ dependencies = [ [[package]] name = "tauri-plugin-updater" -version = "2.10.0" +version = "2.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fe8e9bebd88fc222938ffdfbdcfa0307081423bd01e3252fc337d8bde81fc61" +checksum = "806d9dac662c2e4594ff03c647a552f2c9bd544e7d0f683ec58f872f952ce4af" dependencies = [ "base64 0.22.1", "dirs 6.0.0", @@ -6198,7 +6324,7 @@ dependencies = [ "anyhow", "brotli", "cargo_metadata", - "ctor", + "ctor 0.2.9", "dunce", "glob", "html5ever 0.29.1", @@ -6382,9 +6508,9 @@ dependencies = [ [[package]] name = "tinystr" -version = "0.8.2" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42d3e9c45c09de15d06dd8acf5f4e0e399e85927b7f00711024eb7ae10fa4869" +checksum = "c8323304221c2a851516f22236c5722a72eaa19749016521d6dff0824447d96d" dependencies = [ "displaydoc", "zerovec", @@ -6407,9 +6533,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.50.0" +version = "1.51.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27ad5e34374e03cfffefc301becb44e9dc3c17584f414349ebe29ed26661822d" +checksum = "2bd1c4c0fc4a7ab90fc15ef6daaa3ec3b893f004f915f2392557ed23237820cd" dependencies = [ "bytes", "libc", @@ -6424,9 +6550,9 @@ dependencies = [ [[package]] name = "tokio-macros" -version = "2.6.1" +version = "2.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c55a2eff8b69ce66c84f85e1da1c233edc36ceb85a2058d11b0d6a3c7e7569c" +checksum = "385a6cb71ab9ab790c5fe8d67f1645e6c450a7ce006a33de03daa956cf70a496" dependencies = [ "proc-macro2", "quote", @@ -6490,9 +6616,9 @@ version = "0.9.12+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf92845e79fc2e2def6a5d828f0801e29a2f8acc037becc5ab08595c7d5e9863" dependencies = [ - "indexmap 2.13.0", + "indexmap 2.13.1", "serde_core", - "serde_spanned 1.0.4", + "serde_spanned 1.1.1", "toml_datetime 0.7.5+spec-1.1.0", "toml_parser", "toml_writer", @@ -6519,9 +6645,9 @@ dependencies = [ [[package]] name = "toml_datetime" -version = "1.0.1+spec-1.1.0" +version = "1.1.1+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b320e741db58cac564e26c607d3cc1fdc4a88fd36c879568c07856ed83ff3e9" +checksum = "3165f65f62e28e0115a00b2ebdd37eb6f3b641855f9d636d3cd4103767159ad7" dependencies = [ "serde_core", ] @@ -6532,7 +6658,7 @@ version = "0.19.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" dependencies = [ - "indexmap 2.13.0", + "indexmap 2.13.1", "toml_datetime 0.6.3", "winnow 0.5.40", ] @@ -6543,7 +6669,7 @@ version = "0.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "396e4d48bbb2b7554c944bde63101b5ae446cff6ec4a24227428f15eb72ef338" dependencies = [ - "indexmap 2.13.0", + "indexmap 2.13.1", "serde", "serde_spanned 0.6.9", "toml_datetime 0.6.3", @@ -6552,30 +6678,30 @@ dependencies = [ [[package]] name = "toml_edit" -version = "0.25.5+spec-1.1.0" +version = "0.25.10+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ca1a40644a28bce036923f6a431df0b34236949d111cc07cb6dca830c9ef2e1" +checksum = "a82418ca169e235e6c399a84e395ab6debeb3bc90edc959bf0f48647c6a32d1b" dependencies = [ - "indexmap 2.13.0", - "toml_datetime 1.0.1+spec-1.1.0", + "indexmap 2.13.1", + "toml_datetime 1.1.1+spec-1.1.0", "toml_parser", - "winnow 1.0.0", + "winnow 1.0.1", ] [[package]] name = "toml_parser" -version = "1.0.10+spec-1.1.0" +version = "1.1.2+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7df25b4befd31c4816df190124375d5a20c6b6921e2cad937316de3fccd63420" +checksum = "a2abe9b86193656635d2411dc43050282ca48aa31c2451210f4202550afb7526" dependencies = [ - "winnow 1.0.0", + "winnow 1.0.1", ] [[package]] name = "toml_writer" -version = "1.0.7+spec-1.1.0" +version = "1.1.1+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f17aaa1c6e3dc22b1da4b6bba97d066e354c7945cac2f7852d4e4e7ca7a6b56d" +checksum = "756daf9b1013ebe47a8776667b466417e2d4c5679d441c26230efd9ef78692db" [[package]] name = "tower" @@ -6795,9 +6921,9 @@ checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" [[package]] name = "unicode-segmentation" -version = "1.12.0" +version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" +checksum = "9629274872b2bfaf8d66f5f15725007f635594914870f65218920345aa11aa8c" [[package]] name = "unicode-width" @@ -6961,9 +7087,9 @@ dependencies = [ [[package]] name = "wasm-bindgen" -version = "0.2.114" +version = "0.2.117" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6532f9a5c1ece3798cb1c2cfdba640b9b3ba884f5db45973a6f442510a87d38e" +checksum = "0551fc1bb415591e3372d0bc4780db7e587d84e2a7e79da121051c5c4b89d0b0" dependencies = [ "cfg-if", "once_cell", @@ -6974,23 +7100,19 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.64" +version = "0.4.67" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e9c5522b3a28661442748e09d40924dfb9ca614b21c00d3fd135720e48b67db8" +checksum = "03623de6905b7206edd0a75f69f747f134b7f0a2323392d664448bf2d3c5d87e" dependencies = [ - "cfg-if", - "futures-util", "js-sys", - "once_cell", "wasm-bindgen", - "web-sys", ] [[package]] name = "wasm-bindgen-macro" -version = "0.2.114" +version = "0.2.117" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18a2d50fcf105fb33bb15f00e7a77b772945a2ee45dcf454961fd843e74c18e6" +checksum = "7fbdf9a35adf44786aecd5ff89b4563a90325f9da0923236f6104e603c7e86be" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -6998,9 +7120,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.114" +version = "0.2.117" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03ce4caeaac547cdf713d280eda22a730824dd11e6b8c3ca9e42247b25c631e3" +checksum = "dca9693ef2bab6d4e6707234500350d8dad079eb508dca05530c85dc3a529ff2" dependencies = [ "bumpalo", "proc-macro2", @@ -7011,9 +7133,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.114" +version = "0.2.117" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75a326b8c223ee17883a4251907455a2431acc2791c98c26279376490c378c16" +checksum = "39129a682a6d2d841b6c429d0c51e5cb0ed1a03829d8b3d1e69a011e62cb3d3b" dependencies = [ "unicode-ident", ] @@ -7035,7 +7157,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909" dependencies = [ "anyhow", - "indexmap 2.13.0", + "indexmap 2.13.1", "wasm-encoder", "wasmparser", ] @@ -7061,15 +7183,15 @@ checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" dependencies = [ "bitflags 2.11.0", "hashbrown 0.15.5", - "indexmap 2.13.0", + "indexmap 2.13.1", "semver", ] [[package]] name = "wayland-backend" -version = "0.3.14" +version = "0.3.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa75f400b7f719bcd68b3f47cd939ba654cedeef690f486db71331eec4c6a406" +checksum = "2857dd20b54e916ec7253b3d6b4d5c4d7d4ca2c33c2e11c6c76a99bd8744755d" dependencies = [ "cc", "downcast-rs", @@ -7080,9 +7202,9 @@ dependencies = [ [[package]] name = "wayland-client" -version = "0.31.13" +version = "0.31.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab51d9f7c071abeee76007e2b742499e535148035bb835f97aaed1338cf516c3" +checksum = "645c7c96bb74690c3189b5c9cb4ca1627062bb23693a4fad9d8c3de958260144" dependencies = [ "bitflags 2.11.0", "rustix", @@ -7092,9 +7214,9 @@ dependencies = [ [[package]] name = "wayland-protocols" -version = "0.32.11" +version = "0.32.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b23b5df31ceff1328f06ac607591d5ba360cf58f90c8fad4ac8d3a55a3c4aec7" +checksum = "563a85523cade2429938e790815fd7319062103b9f4a2dc806e9b53b95982d8f" dependencies = [ "bitflags 2.11.0", "wayland-backend", @@ -7104,9 +7226,9 @@ dependencies = [ [[package]] name = "wayland-protocols-wlr" -version = "0.3.11" +version = "0.3.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78248e4cc0eff8163370ba5c158630dcae1f3497a586b826eca2ef5f348d6235" +checksum = "eb04e52f7836d7c7976c78ca0250d61e33873c34156a2a1fc9474828ec268234" dependencies = [ "bitflags 2.11.0", "wayland-backend", @@ -7117,9 +7239,9 @@ dependencies = [ [[package]] name = "wayland-scanner" -version = "0.31.9" +version = "0.31.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c86287151a309799b821ca709b7345a048a2956af05957c89cb824ab919fa4e3" +checksum = "9c324a910fd86ebdc364a3e61ec1f11737d3b1d6c273c0239ee8ff4bc0d24b4a" dependencies = [ "proc-macro2", "quick-xml 0.39.2", @@ -7128,18 +7250,18 @@ dependencies = [ [[package]] name = "wayland-sys" -version = "0.31.10" +version = "0.31.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "374f6b70e8e0d6bf9461a32988fd553b59ff630964924dad6e4a4eb6bd538d17" +checksum = "d8eab23fefc9e41f8e841df4a9c707e8a8c4ed26e944ef69297184de2785e3be" dependencies = [ "pkg-config", ] [[package]] name = "web-sys" -version = "0.3.91" +version = "0.3.94" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "854ba17bb104abfb26ba36da9729addc7ce7f06f5c0f90f3c391f8461cca21f9" +checksum = "cd70027e39b12f0849461e08ffc50b9cd7688d942c1c8e3c7b22273236b4dd0a" dependencies = [ "js-sys", "wasm-bindgen", @@ -7756,9 +7878,9 @@ dependencies = [ [[package]] name = "winnow" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a90e88e4667264a994d34e6d1ab2d26d398dcdca8b7f52bec8668957517fc7d8" +checksum = "09dac053f1cd375980747450bfc7250c264eaae0583872e845c0c7cd578872b5" dependencies = [ "memchr", ] @@ -7810,7 +7932,7 @@ checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21" dependencies = [ "anyhow", "heck 0.5.0", - "indexmap 2.13.0", + "indexmap 2.13.1", "prettyplease", "syn 2.0.117", "wasm-metadata", @@ -7841,7 +7963,7 @@ checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" dependencies = [ "anyhow", "bitflags 2.11.0", - "indexmap 2.13.0", + "indexmap 2.13.1", "log", "serde", "serde_derive", @@ -7860,7 +7982,7 @@ checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736" dependencies = [ "anyhow", "id-arena", - "indexmap 2.13.0", + "indexmap 2.13.1", "log", "semver", "serde", @@ -7890,9 +8012,9 @@ dependencies = [ [[package]] name = "writeable" -version = "0.6.2" +version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9" +checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4" [[package]] name = "wry" @@ -7988,9 +8110,9 @@ dependencies = [ [[package]] name = "yoke" -version = "0.8.1" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72d6e5c6afb84d73944e5cedb052c4680d5657337201555f9f2a16b7406d4954" +checksum = "abe8c5fda708d9ca3df187cae8bfb9ceda00dd96231bed36e445a1a48e66f9ca" dependencies = [ "stable_deref_trait", "yoke-derive", @@ -7999,9 +8121,9 @@ dependencies = [ [[package]] name = "yoke-derive" -version = "0.8.1" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d" +checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e" dependencies = [ "proc-macro2", "quote", @@ -8072,18 +8194,18 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.8.47" +version = "0.8.48" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "efbb2a062be311f2ba113ce66f697a4dc589f85e78a4aea276200804cea0ed87" +checksum = "eed437bf9d6692032087e337407a86f04cd8d6a16a37199ed57949d415bd68e9" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.47" +version = "0.8.48" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e8bc7269b54418e7aeeef514aa68f8690b8c0489a06b0136e5f57c4c5ccab89" +checksum = "70e3cd084b1788766f53af483dd21f93881ff30d7320490ec3ef7526d203bad4" dependencies = [ "proc-macro2", "quote", @@ -8092,18 +8214,18 @@ dependencies = [ [[package]] name = "zerofrom" -version = "0.1.6" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" +checksum = "69faa1f2a1ea75661980b013019ed6687ed0e83d069bc1114e2cc74c6c04c4df" dependencies = [ "zerofrom-derive", ] [[package]] name = "zerofrom-derive" -version = "0.1.6" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" +checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1" dependencies = [ "proc-macro2", "quote", @@ -8133,9 +8255,9 @@ dependencies = [ [[package]] name = "zerotrie" -version = "0.2.3" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a59c17a5562d507e4b54960e8569ebee33bee890c70aa3fe7b97e85a9fd7851" +checksum = "0f9152d31db0792fa83f70fb2f83148effb5c1f5b8c7686c3459e361d9bc20bf" dependencies = [ "displaydoc", "yoke", @@ -8144,9 +8266,9 @@ dependencies = [ [[package]] name = "zerovec" -version = "0.11.5" +version = "0.11.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c28719294829477f525be0186d13efa9a3c602f7ec202ca9e353d310fb9a002" +checksum = "90f911cbc359ab6af17377d242225f4d75119aec87ea711a880987b18cd7b239" dependencies = [ "yoke", "zerofrom", @@ -8155,9 +8277,9 @@ dependencies = [ [[package]] name = "zerovec-derive" -version = "0.11.2" +version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3" +checksum = "625dc425cab0dca6dc3c3319506e6593dcb08a9f387ea3b284dbd52a92c40555" dependencies = [ "proc-macro2", "quote", @@ -8172,15 +8294,15 @@ checksum = "caa8cd6af31c3b31c6631b8f483848b91589021b28fffe50adada48d4f4d2ed1" dependencies = [ "arbitrary", "crc32fast", - "indexmap 2.13.0", + "indexmap 2.13.1", "memchr", ] [[package]] name = "zip" -version = "8.4.0" +version = "8.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7756d0206d058333667493c4014f545f4b9603c4330ccd6d9b3f86dcab59f7d9" +checksum = "2726508a48f38dceb22b35ecbbd2430efe34ff05c62bd3285f965d7911b33464" dependencies = [ "aes", "bzip2", @@ -8190,7 +8312,7 @@ dependencies = [ "flate2", "getrandom 0.4.2", "hmac", - "indexmap 2.13.0", + "indexmap 2.13.1", "lzma-rust2", "memchr", "pbkdf2", @@ -8263,9 +8385,9 @@ checksum = "cb8a0807f7c01457d0379ba880ba6322660448ddebc890ce29bb64da71fb40f9" [[package]] name = "zune-jpeg" -version = "0.5.13" +version = "0.5.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec5f41c76397b7da451efd19915684f727d7e1d516384ca6bd0ec43ec94de23c" +checksum = "27bc9d5b815bc103f142aa054f561d9187d191692ec7c2d1e2b4737f8dbd7296" dependencies = [ "zune-core", ] diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index 35bc1eba..e235e400 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -6,10 +6,14 @@ authors = ["Demir Yerli "] edition = "2024" [workspace] -members = ["crates/quadrant-core"] +members = [ + "crates/quadrant-core", + "crates/quadrant-host", + "crates/quadrant-napi", +] [workspace.package] -version = "26.4.0-stable" +version = "26.4.1-preview.0" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -23,7 +27,7 @@ crate-type = ["staticlib", "cdylib", "rlib"] [features] default = ["proprietary"] telemetry = [] -curseforge = ["quadrant-core/curseforge"] +curseforge = ["quadrant-core/curseforge", "quadrant-host/curseforge"] # Quadrant ID/Sync/Share quadrant_id = [] updater = [] @@ -38,6 +42,7 @@ tauri-build = { version = "2.5.6", features = [] } tauri = { version = "2.10.3", features = ["tray-icon", "config-json5"] } quadrant-core = { path = "crates/quadrant-core" } +quadrant-host = { path = "crates/quadrant-host" } tauri-plugin-opener = "2.5.3" serde = { version = "1.0.228", features = ["derive"] } @@ -46,13 +51,13 @@ chrono = { version = "0.4.44", features = ["serde"] } anyhow = { version = "1.0.102" } dirs = "6.0.0" log = "0.4.29" -tauri-plugin-fs = { version = "2.4.5", features = ["watch"] } +tauri-plugin-fs = { version = "2.5.0", features = ["watch"] } tauri-plugin-os = "2.3.2" rss = "2.0.12" tauri-plugin-store = "2.4.2" uuid = { version = "1.23.0", features = ["v7"] } -tauri-plugin-http = "2.5.7" -tokio = { version = "1.50.0", features = ["full"] } +tauri-plugin-http = "2.5.8" +tokio = { version = "1.51.0", features = ["full"] } urlencoding = "2.1.3" open = "5.3.3" futures = { version = "0.3.32" } @@ -63,8 +68,8 @@ tokio-tungstenite = { version = "0.29.0", features = [ sha1 = "0.11.0" hex = "0.4.3" colog = "1.4.0" -tauri-plugin-deep-link = { version = "2.4.7" } -tauri-plugin-dialog = "2.6.0" +tauri-plugin-deep-link = { version = "2.4.8" } +tauri-plugin-dialog = "2.7.0" keyring = { version = "3.6.3", features = [ "apple-native", "windows-native", @@ -72,7 +77,7 @@ keyring = { version = "3.6.3", features = [ ] } tauri-plugin-clipboard-manager = "2.3.2" tauri-plugin-notification = "2.3.3" -tauri-plugin-persisted-scope = "2.3.5" +tauri-plugin-persisted-scope = "2.3.6" http-cache-reqwest = { version = "1.0.0-alpha.5", default-features = false, features = [ "manager-moka", "url-standard", @@ -88,15 +93,15 @@ reqwest = { version = "0.13.2", features = [ ] } once_cell = "1.21.4" tauri-plugin-oauth = "2" -zip = "8.4.0" +zip = "8.5.0" url = { version = "2.5.8", features = ["serde", "std"] } [target.'cfg(not(any(target_os = "android", target_os = "ios")))'.dependencies] -tauri-plugin-single-instance = { version = "2.4.0", features = ["deep-link"] } +tauri-plugin-single-instance = { version = "2.4.1", features = ["deep-link"] } tauri-plugin-autostart = "2.5.1" tauri-plugin-cli = "2.4.1" -tauri-plugin-updater = "2.10.0" +tauri-plugin-updater = "2.10.1" [profile.dev] opt-level = 0 diff --git a/src-tauri/capabilities/default.json b/src-tauri/capabilities/default.json index 1ef38889..01570edc 100644 --- a/src-tauri/capabilities/default.json +++ b/src-tauri/capabilities/default.json @@ -2,9 +2,7 @@ "$schema": "../gen/schemas/desktop-schema.json", "identifier": "default", "description": "Capability for the main window", - "windows": [ - "main" - ], + "windows": ["main"], "permissions": [ "core:default", "opener:default", @@ -49,6 +47,7 @@ "core:window:allow-set-focus", "dialog:default", "clipboard-manager:allow-read-text", + "clipboard-manager:allow-write-text", "notification:default", "core:window:allow-start-dragging", "core:window:allow-hide", @@ -59,9 +58,7 @@ "oauth:allow-cancel", { "identifier": "http:default", - "allow": [ - "https://api.mrquantumoff.dev/api/v3/**" - ] + "allow": ["https://api.mrquantumoff.dev/api/v3/**"] } ] -} \ No newline at end of file +} diff --git a/src-tauri/capabilities/desktop.json b/src-tauri/capabilities/desktop.json index 482e2d13..f0b7ba6d 100644 --- a/src-tauri/capabilities/desktop.json +++ b/src-tauri/capabilities/desktop.json @@ -1,16 +1,6 @@ { "identifier": "desktop-capability", - "platforms": [ - "macOS", - "windows", - "linux" - ], - "windows": [ - "main" - ], - "permissions": [ - "autostart:default", - "cli:default", - "updater:default" - ] -} \ No newline at end of file + "platforms": ["macOS", "windows", "linux"], + "windows": ["main"], + "permissions": ["autostart:default", "cli:default", "updater:default"] +} diff --git a/src-tauri/crates/quadrant-core/Cargo.toml b/src-tauri/crates/quadrant-core/Cargo.toml index 665016c9..327b7894 100644 --- a/src-tauri/crates/quadrant-core/Cargo.toml +++ b/src-tauri/crates/quadrant-core/Cargo.toml @@ -18,7 +18,7 @@ http-cache-reqwest = { version = "1.0.0-alpha.5", default-features = false, feat "url-standard", ] } log = "0.4.29" -moka = { version = "0.12.14", features = ["future"] } +moka = { version = "0.12.15", features = ["future"] } once_cell = "1.21.4" reqwest = { version = "0.13.2", features = [ "json", @@ -33,12 +33,12 @@ rss = "2.0.12" serde = { version = "1.0.228", features = ["derive"] } serde_json = "1.0.149" sha1 = "0.11.0" -tokio = { version = "1.50.0", features = ["sync"] } +tokio = { version = "1.51.0", features = ["sync"] } urlencoding = "2.1.3" uuid = { version = "1.23.0", features = ["v7"] } -zip = "8.4.0" +zip = "8.5.0" [dev-dependencies] httpmock = "0.8.3" tempfile = "3.27.0" -tokio = { version = "1.50.0", features = ["macros", "rt-multi-thread", "sync"] } +tokio = { version = "1.51.0", features = ["macros", "rt-multi-thread", "sync"] } diff --git a/src-tauri/crates/quadrant-core/src/account/id.rs b/src-tauri/crates/quadrant-core/src/account/id.rs index e167d5f5..292e248b 100644 --- a/src-tauri/crates/quadrant-core/src/account/id.rs +++ b/src-tauri/crates/quadrant-core/src/account/id.rs @@ -1,13 +1,13 @@ //! Account identity, login, refresh, and notification APIs. -use std::{collections::HashMap, env}; +use std::collections::HashMap; use reqwest::StatusCode; use serde::{Deserialize, Deserializer, Serialize}; use crate::{ Result, - account::{QNT_BASE_URL, get_account_token, get_refresh_token, set_secret}, + account::{backend_base_url, get_account_token, get_refresh_token, set_secret}, ports::SecretStore, }; @@ -19,10 +19,6 @@ const EMPTY_NOTIFICATION_HISTORY_BODY: &str = "Notification history request retu const EMPTY_ACCOUNT_INFO_BODY: &str = "Account info request returned an empty body"; const EMPTY_OAUTH_BODY: &str = "OAuth2 token endpoint returned an empty body"; -fn backend_base_url() -> String { - env::var("QUADRANT_API_BASE_URL").unwrap_or_else(|_| QNT_BASE_URL.to_string()) -} - /// Account profile returned by the Quadrant backend. #[derive(Debug, Clone, Serialize, Deserialize)] pub struct AccountInfo { @@ -143,10 +139,12 @@ fn notification_cursor_from_last( notifications: &[Notification], fallback: NotificationCursor, ) -> NotificationCursor { - notifications.last().map_or(fallback, |last| NotificationCursor { - created_at: Some(last.created_at.clone()), - notification_id: Some(last.notification_id.clone()), - }) + notifications + .last() + .map_or(fallback, |last| NotificationCursor { + created_at: Some(last.created_at.clone()), + notification_id: Some(last.notification_id.clone()), + }) } /// Paginated notification history response. @@ -345,6 +343,30 @@ pub async fn get_notification_history_page( read: Option, limit: Option, include_modpack_sync: bool, +) -> Result { + get_notification_history_page_with_refresh( + secret_store, + user_agent, + cursor, + read, + limit, + include_modpack_sync, + env!("QUADRANT_OAUTH2_CLIENT_ID"), + env!("QUADRANT_OAUTH2_CLIENT_SECRET"), + ) + .await +} + +/// Fetches a single page of notification history and refreshes the token on demand. +pub async fn get_notification_history_page_with_refresh( + secret_store: &impl SecretStore, + user_agent: &str, + cursor: Option<&NotificationCursor>, + read: Option, + limit: Option, + include_modpack_sync: bool, + client_id: &str, + client_secret: &str, ) -> Result { let token = get_account_token(secret_store)?; let query = notification_history_query(cursor, read, limit, include_modpack_sync); @@ -359,13 +381,7 @@ pub async fn get_notification_history_page( if response.status() == StatusCode::UNAUTHORIZED { log::info!("Notification history request unauthorized, attempting token refresh"); - try_refresh_token( - secret_store, - env!("QUADRANT_OAUTH2_CLIENT_ID"), - env!("QUADRANT_OAUTH2_CLIENT_SECRET"), - user_agent, - ) - .await?; + try_refresh_token(secret_store, client_id, client_secret, user_agent).await?; let new_token = get_account_token(secret_store)?; let retry = reqwest::Client::new() .get(format!("{}/account/notifications/get", backend_base_url())) @@ -387,18 +403,42 @@ pub async fn get_notification_history_all_since( cursor: Option<&NotificationCursor>, read: Option, include_modpack_sync: bool, +) -> Result<(Vec, NotificationCursor)> { + get_notification_history_all_since_with_refresh( + secret_store, + user_agent, + cursor, + read, + include_modpack_sync, + env!("QUADRANT_OAUTH2_CLIENT_ID"), + env!("QUADRANT_OAUTH2_CLIENT_SECRET"), + ) + .await +} + +/// Fetches all notification history pages from the provided cursor onward. +pub async fn get_notification_history_all_since_with_refresh( + secret_store: &impl SecretStore, + user_agent: &str, + cursor: Option<&NotificationCursor>, + read: Option, + include_modpack_sync: bool, + client_id: &str, + client_secret: &str, ) -> Result<(Vec, NotificationCursor)> { let mut page_cursor = cursor.cloned().unwrap_or_default(); let mut notifications = Vec::new(); loop { - let page = get_notification_history_page( + let page = get_notification_history_page_with_refresh( secret_store, user_agent, Some(&page_cursor), read, Some(DEFAULT_NOTIFICATION_HISTORY_LIMIT), include_modpack_sync, + client_id, + client_secret, ) .await?; @@ -419,11 +459,21 @@ pub async fn get_notification_history_all_since( async fn parse_notification_history_response( response: reqwest::Response, ) -> Result { - parse_json_response(response, "Notification history request failed", EMPTY_NOTIFICATION_HISTORY_BODY).await + parse_json_response( + response, + "Notification history request failed", + EMPTY_NOTIFICATION_HISTORY_BODY, + ) + .await } async fn parse_account_info_response(response: reqwest::Response) -> Result { - parse_json_response(response, "Account info request failed", EMPTY_ACCOUNT_INFO_BODY).await + parse_json_response( + response, + "Account info request failed", + EMPTY_ACCOUNT_INFO_BODY, + ) + .await } async fn parse_oauth_token_response( diff --git a/src-tauri/crates/quadrant-core/src/account/mod.rs b/src-tauri/crates/quadrant-core/src/account/mod.rs index 7337c6c8..72c4a60c 100644 --- a/src-tauri/crates/quadrant-core/src/account/mod.rs +++ b/src-tauri/crates/quadrant-core/src/account/mod.rs @@ -1,5 +1,7 @@ //! Account token helpers and Quadrant cloud entrypoints. +use std::env; + use crate::{Result, ports::SecretStore}; pub mod id; @@ -12,6 +14,11 @@ pub const QNT_BASE_URL: &str = "https://api.usequadrant.dev/api/v3"; /// Stable keyring service name used by the current app. pub const KEYRING_SERVICE: &str = "dev.mrquantumoff.mcmodpackmanager"; +/// Resolves the backend base URL, preferring an explicit runtime override. +pub fn backend_base_url() -> String { + env::var("QUADRANT_API_BASE_URL").unwrap_or_else(|_| QNT_BASE_URL.to_string()) +} + /// Persists a named secret in the host secret store. pub fn set_secret(secret_store: &impl SecretStore, key: &str, value: &str) -> Result<()> { log::info!("Setting secret: {key}"); diff --git a/src-tauri/crates/quadrant-core/src/account/quadrant_settings_sync.rs b/src-tauri/crates/quadrant-core/src/account/quadrant_settings_sync.rs index 16f6a697..df06d245 100644 --- a/src-tauri/crates/quadrant-core/src/account/quadrant_settings_sync.rs +++ b/src-tauri/crates/quadrant-core/src/account/quadrant_settings_sync.rs @@ -8,7 +8,7 @@ use serde_json::{Value, json}; use crate::{ Result, - account::{QNT_BASE_URL, get_account_token}, + account::{backend_base_url, get_account_token}, ports::{SecretStore, SettingsStore}, }; @@ -40,7 +40,7 @@ pub async fn get_quadrant_settings( log::debug!("Pulling settings from cloud"); let token = get_account_token(secret_store)?; let json = reqwest::Client::new() - .get(format!("{}/quadrant/settings_sync/get", QNT_BASE_URL)) + .get(format!("{}/quadrant/settings_sync/get", backend_base_url())) .header("User-Agent", user_agent) .bearer_auth(token) .send() @@ -104,7 +104,10 @@ pub async fn submit_quadrant_settings( } let response = reqwest::Client::new() - .post(format!("{}/quadrant/settings_sync/submit", QNT_BASE_URL)) + .post(format!( + "{}/quadrant/settings_sync/submit", + backend_base_url() + )) .header("User-Agent", user_agent) .bearer_auth(token) .json(&json!({ diff --git a/src-tauri/crates/quadrant-core/src/account/quadrant_share.rs b/src-tauri/crates/quadrant-core/src/account/quadrant_share.rs index 6395a8f6..ad7de52a 100644 --- a/src-tauri/crates/quadrant-core/src/account/quadrant_share.rs +++ b/src-tauri/crates/quadrant-core/src/account/quadrant_share.rs @@ -1,10 +1,11 @@ //! Quadrant Share submission and retrieval APIs. +use serde::de::DeserializeOwned; use serde::{Deserialize, Serialize}; use crate::{ Result, - account::QNT_BASE_URL, + account::backend_base_url, models::InstalledModpack, ports::{SecretStore, SettingsStore}, }; @@ -36,6 +37,53 @@ pub struct QuadrantShareResponse { pub mod_config: String, } +const EMPTY_SHARE_SUBMISSION_BODY: &str = "Quadrant Share submission returned an empty body"; +const EMPTY_SHARE_GET_BODY: &str = "Quadrant Share retrieval returned an empty body"; + +async fn parse_json_response( + response: reqwest::Response, + error_prefix: &str, + empty_body_error: &str, +) -> Result { + let status = response.status(); + let response_raw = response.text().await?; + if !status.is_success() { + return Err(anyhow::anyhow!( + "{} with {}: {}", + error_prefix, + status, + response_preview(&response_raw) + )); + } + + let trimmed = response_raw.trim(); + if trimmed.is_empty() { + return Err(anyhow::anyhow!("{empty_body_error}")); + } + + serde_json::from_str(trimmed).map_err(|error| { + anyhow::anyhow!( + "{}: invalid JSON response: {}; body preview: {}", + error_prefix, + error, + response_preview(trimmed) + ) + }) +} + +fn response_preview(body: &str) -> String { + let trimmed = body.trim(); + if trimmed.is_empty() { + return "".to_string(); + } + + if trimmed.len() > 300 { + trimmed[..300].to_string() + } else { + trimmed.to_string() + } +} + /// Submits a modpack to Quadrant Share using the current settings and account state. pub async fn share_modpack_raw( settings_store: &impl SettingsStore, @@ -52,7 +100,7 @@ pub async fn share_modpack_raw( } let token = secret_store.get_secret("accountToken")?; - let mut url = format!("{}/quadrant/share/submit", QNT_BASE_URL); + let mut url = format!("{}/quadrant/share/submit", backend_base_url()); if token.is_some() { url = format!("{}/id", url); } @@ -60,10 +108,10 @@ pub async fn share_modpack_raw( let mut request = reqwest::Client::new() .post(&url) .header("User-Agent", user_agent) - .body(serde_json::to_string_pretty(&QuadrantShareSubmission { + .json(&QuadrantShareSubmission { hardware_id: settings_store.get_string("hardwareId")?.unwrap_or_default(), mod_config: serde_json::to_string_pretty(&mod_config)?, - })?); + }); if let Some(token) = token { request = request.bearer_auth(token); @@ -71,11 +119,12 @@ pub async fn share_modpack_raw( request = request.header("Authorization", api_key); } - Ok(request - .send() - .await? - .json::() - .await?) + parse_json_response( + request.send().await?, + "Quadrant Share submission failed", + EMPTY_SHARE_SUBMISSION_BODY, + ) + .await } /// Resolves a share code into an installed modpack manifest. @@ -85,15 +134,153 @@ pub async fn get_quadrant_share_modpack( code: String, ) -> Result { log::info!("Fetching shared modpack with code: {code}"); - let response = reqwest::Client::new() - .get(format!("{}/quadrant/share/get", QNT_BASE_URL)) - .query(&[("code", code)]) - .header("User-Agent", user_agent) - .header("Authorization", api_key) - .send() - .await? - .text() - .await?; - let response: QuadrantShareResponse = serde_json::from_str(&response)?; - Ok(serde_json::from_str(&response.mod_config)?) + let response: QuadrantShareResponse = parse_json_response( + reqwest::Client::new() + .get(format!("{}/quadrant/share/get", backend_base_url())) + .query(&[("code", code)]) + .header("User-Agent", user_agent) + .header("Authorization", api_key) + .send() + .await?, + "Quadrant Share retrieval failed", + EMPTY_SHARE_GET_BODY, + ) + .await?; + + serde_json::from_str(&response.mod_config).map_err(|error| { + anyhow::anyhow!( + "Quadrant Share retrieval failed: invalid modpack payload: {}; body preview: {}", + error, + response_preview(&response.mod_config) + ) + }) +} + +#[cfg(test)] +mod tests { + use super::{get_quadrant_share_modpack, share_modpack_raw}; + use crate::{ + Result, + models::{InstalledMod, InstalledModpack, ModLoader, ModSource}, + ports::{SecretStore, SettingsStore}, + }; + use httpmock::{ + Method::{GET, POST}, + MockServer, + }; + use serde_json::Value; + use std::sync::Mutex; + + static ENV_LOCK: Mutex<()> = Mutex::new(()); + + struct MemorySettingsStore; + + impl SettingsStore for MemorySettingsStore { + fn get_value(&self, key: &str) -> Result> { + Ok(match key { + "collectUserData" => Some(Value::Bool(true)), + "hardwareId" => Some(Value::String("hw-123".to_string())), + _ => None, + }) + } + + fn set_value(&self, _key: &str, _value: Value) -> Result<()> { + Ok(()) + } + + fn entries(&self) -> Result> { + Ok(vec![]) + } + } + + struct EmptySecretStore; + + impl SecretStore for EmptySecretStore { + fn get_secret(&self, _key: &str) -> Result> { + Ok(None) + } + + fn set_secret(&self, _key: &str, _value: &str) -> Result<()> { + Ok(()) + } + + fn delete_secret(&self, _key: &str) -> Result<()> { + Ok(()) + } + } + + fn installed_modpack() -> InstalledModpack { + InstalledModpack { + name: "Test Pack".to_string(), + version: "1.20.1".to_string(), + mod_loader: ModLoader::Fabric, + mods: vec![InstalledMod { + id: "mod-1".to_string(), + source: ModSource::Modrinth, + download_url: "https://example.com/mod.jar".to_string(), + }], + } + } + + #[tokio::test] + async fn share_modpack_raw_sends_json_and_surfaces_http_body() { + let _guard = ENV_LOCK.lock().unwrap(); + let server = MockServer::start(); + unsafe { + std::env::set_var("QUADRANT_API_BASE_URL", server.base_url()); + } + + let _mock = server.mock(|when, then| { + when.method(POST) + .path("/quadrant/share/submit") + .header("authorization", "test-api-key") + .header("content-type", "application/json"); + then.status(500) + .header("content-type", "text/plain") + .body("share backend exploded"); + }); + + let error = share_modpack_raw( + &MemorySettingsStore, + &EmptySecretStore, + "test-agent", + installed_modpack(), + "test-api-key", + ) + .await + .unwrap_err(); + + let message = error.to_string(); + assert!( + message.contains("Quadrant Share submission failed with 500 Internal Server Error") + ); + assert!(message.contains("share backend exploded")); + } + + #[tokio::test] + async fn get_quadrant_share_modpack_surfaces_invalid_success_payload() { + let _guard = ENV_LOCK.lock().unwrap(); + let server = MockServer::start(); + unsafe { + std::env::set_var("QUADRANT_API_BASE_URL", server.base_url()); + } + + let _mock = server.mock(|when, then| { + when.method(GET) + .path("/quadrant/share/get") + .query_param("code", "abc123") + .header("authorization", "test-api-key"); + then.status(200) + .header("content-type", "application/json") + .body(r#"{"code":123,"mod_config":"not-json"}"#); + }); + + let error = get_quadrant_share_modpack("test-agent", "test-api-key", "abc123".to_string()) + .await + .unwrap_err(); + + let message = error.to_string(); + assert!(message.contains("Quadrant Share retrieval failed: invalid modpack payload")); + assert!(message.contains("not-json")); + } } diff --git a/src-tauri/crates/quadrant-core/src/mc_mod/curseforge.rs b/src-tauri/crates/quadrant-core/src/mc_mod/curseforge.rs index c2e30018..83a6ae76 100644 --- a/src-tauri/crates/quadrant-core/src/mc_mod/curseforge.rs +++ b/src-tauri/crates/quadrant-core/src/mc_mod/curseforge.rs @@ -141,9 +141,7 @@ pub async fn get_mod_curseforge(args: GetModArgs) -> Result { .collect(); let logo = res_data["logo"]["url"] .as_str() - .unwrap_or( - "https://raw.githubusercontent.com/mrquantumoff/quadrant/next/public/logonobg.png", - ) + .unwrap_or("https://raw.githubusercontent.com/quadrantmc/quadrant/next/public/logonobg.png") .to_string(); Ok(Mod { id: args.id, diff --git a/src-tauri/crates/quadrant-core/src/mc_mod/mod.rs b/src-tauri/crates/quadrant-core/src/mc_mod/mod.rs index 8815faa2..5249fd95 100644 --- a/src-tauri/crates/quadrant-core/src/mc_mod/mod.rs +++ b/src-tauri/crates/quadrant-core/src/mc_mod/mod.rs @@ -214,7 +214,7 @@ pub struct IdentifiedMod { /// Returns the default Quadrant user agent used for upstream requests. pub fn get_user_agent() -> String { format!( - "mrquantumoff/quadrant/v{} (mrquantumoff.dev) (QUADRANT NEXT)", + "mrquantumoff/quadrant/v{} (mrquantumoff.dev) (QUADRANT NEXT/TAURI)", env!("CARGO_PKG_VERSION") ) } diff --git a/src-tauri/crates/quadrant-core/src/mc_mod/modrinth.rs b/src-tauri/crates/quadrant-core/src/mc_mod/modrinth.rs index 9eb002bd..a28a0a3f 100644 --- a/src-tauri/crates/quadrant-core/src/mc_mod/modrinth.rs +++ b/src-tauri/crates/quadrant-core/src/mc_mod/modrinth.rs @@ -79,7 +79,9 @@ pub async fn search_mods_modrinth( let icon = mod_data["icon_url"] .as_str() .filter(|value| !value.trim().is_empty()) - .unwrap_or("https://raw.githubusercontent.com/mrquantumoff/quadrant/next/public/logonobg.png") + .unwrap_or( + "https://raw.githubusercontent.com/quadrantmc/quadrant/next/public/logonobg.png", + ) .to_string(); let slug = mod_data["slug"].as_str().unwrap_or_default().to_string(); diff --git a/src-tauri/crates/quadrant-core/src/models.rs b/src-tauri/crates/quadrant-core/src/models.rs index 7479adb8..69af17d3 100644 --- a/src-tauri/crates/quadrant-core/src/models.rs +++ b/src-tauri/crates/quadrant-core/src/models.rs @@ -1,7 +1,8 @@ //! Shared serialization-friendly data models used across core services. use chrono::{DateTime, Utc}; -use serde::{Deserialize, Serialize}; +use serde::{Deserialize, Deserializer, Serialize, de::Error as DeError}; +use serde_json::Value; use std::path::{Path, PathBuf}; /// Source provider for a mod or downloadable file. @@ -116,6 +117,7 @@ pub struct LocalModpack { /// Whether this modpack is currently applied as the active `mods` folder. pub is_applied: bool, /// Last successful sync time in milliseconds since the Unix epoch. + #[serde(deserialize_with = "deserialize_integral_i64")] pub last_synced: i64, /// Stable synced modpack identifier when this modpack is linked to Quadrant Sync. pub modpack_id: Option, @@ -157,6 +159,36 @@ impl From<(InstalledModpack, bool, i64)> for LocalModpack { } } +fn deserialize_integral_i64<'de, D>(deserializer: D) -> Result +where + D: Deserializer<'de>, +{ + let value = Value::deserialize(deserializer)?; + match value { + Value::Number(number) => { + if let Some(value) = number.as_i64() { + return Ok(value); + } + if let Some(value) = number.as_u64() { + return i64::try_from(value) + .map_err(|_| D::Error::custom("integer is out of range for i64")); + } + if let Some(value) = number.as_f64() + && value.is_finite() + && value.fract() == 0.0 + && value >= i64::MIN as f64 + && value <= i64::MAX as f64 + { + return Ok(value as i64); + } + Err(D::Error::custom("expected an integer-valued number")) + } + other => Err(D::Error::custom(format!( + "expected a number for integer deserialization, got {other}" + ))), + } +} + /// RSS article surfaced by the Quadrant news feed. #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct Article { diff --git a/src-tauri/crates/quadrant-core/src/modpacks.rs b/src-tauri/crates/quadrant-core/src/modpacks.rs index 82cfe301..85217e63 100644 --- a/src-tauri/crates/quadrant-core/src/modpacks.rs +++ b/src-tauri/crates/quadrant-core/src/modpacks.rs @@ -181,7 +181,9 @@ pub fn update_modpack( version: Option, mod_loader: Option, ) -> Result { - log::info!("Updating modpack \"{modpack_source}\" (name={name:?}, version={version:?}, mod_loader={mod_loader:?})"); + log::info!( + "Updating modpack \"{modpack_source}\" (name={name:?}, version={version:?}, mod_loader={mod_loader:?})" + ); let mut modpack = existing_modpacks .iter() .find(|modpack| modpack.name == modpack_source) @@ -337,7 +339,11 @@ pub async fn install_modpack( settings: &impl SettingsStore, event_sink: &impl EventSink, ) -> Result<()> { - log::info!("Installing modpack \"{}\" ({} mod(s))", mod_config.name, mod_config.mods.len()); + log::info!( + "Installing modpack \"{}\" ({} mod(s))", + mod_config.name, + mod_config.mods.len() + ); let modpack_folder = modpack_path(mc_folder, &mod_config.name); if !modpack_folder.exists() { std::fs::create_dir_all(&modpack_folder)?; @@ -405,7 +411,10 @@ pub fn export_modpack_to( destination: &Path, event_sink: &impl EventSink, ) -> Result<()> { - log::info!("Exporting modpack \"{modpack}\" to {}", destination.display()); + log::info!( + "Exporting modpack \"{modpack}\" to {}", + destination.display() + ); let modpack_folder = modpack_path(mc_folder, modpack); let destination = std::fs::File::create(destination)?; let mut zip = zip::ZipWriter::new(destination); diff --git a/src-tauri/crates/quadrant-core/src/telemetry.rs b/src-tauri/crates/quadrant-core/src/telemetry.rs index 8cf30f40..90465dcb 100644 --- a/src-tauri/crates/quadrant-core/src/telemetry.rs +++ b/src-tauri/crates/quadrant-core/src/telemetry.rs @@ -3,7 +3,9 @@ use chrono::prelude::*; use serde::{Deserialize, Serialize}; -use crate::{Result, account::QNT_BASE_URL, ports::SettingsStore}; +use crate::{ + Result, account::QNT_BASE_URL, mc_mod::http::provider_http_client, ports::SettingsStore, +}; /// Telemetry payload submitted to the Quadrant backend. #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] @@ -40,17 +42,10 @@ pub async fn get_telemetry_info( version: String, os: String, ) -> Result { - let hardware_id = settings_store - .get_string("hardwareId")? - .unwrap_or_default(); + let hardware_id = settings_store.get_string("hardwareId")?.unwrap_or_default(); let date_time = Utc::now(); - let country_info = reqwest::Client::new() - .get("https://ipinfo.io/json") - .send() - .await? - .json::() - .await?; + let country = fetch_country_code().await; Ok(AppInfo { version, @@ -61,10 +56,37 @@ pub async fn get_telemetry_info( manual_input_usage: 0, hardware_id, date: date_time, - country: country_info.country, + country, }) } +async fn fetch_country_code() -> String { + match provider_http_client() + .get("https://ipapi.co/json") + .send() + .await + { + Ok(response) => match response.error_for_status() { + Ok(response) => match response.json::().await { + Ok(payload) if !payload.country.trim().is_empty() => payload.country, + Ok(_) => "unknown".to_string(), + Err(error) => { + log::warn!("Failed to decode telemetry country lookup response: {error}"); + "unknown".to_string() + } + }, + Err(error) => { + log::warn!("Telemetry country lookup failed: {error}"); + "unknown".to_string() + } + }, + Err(error) => { + log::warn!("Telemetry country lookup request failed: {error}"); + "unknown".to_string() + } + } +} + /// Sends telemetry data when collection is enabled in settings. pub async fn send_telemetry( settings_store: &impl SettingsStore, @@ -80,13 +102,14 @@ pub async fn send_telemetry( log::info!("Sending telemetry for version {version} on {os}"); let info = get_telemetry_info(settings_store, version, os).await?; - reqwest::Client::new() + provider_http_client() .post(format!("{}/quadrant/usage/submit", QNT_BASE_URL)) .json(&info) .header("Authorization", api_key) .header("User-Agent", user_agent) .send() - .await?; + .await? + .error_for_status()?; Ok(()) } @@ -98,17 +121,16 @@ pub async fn remove_telemetry( api_key: &str, ) -> Result<()> { log::info!("Removing telemetry data"); - let hardware_id = settings_store - .get_string("hardwareId")? - .unwrap_or_default(); + let hardware_id = settings_store.get_string("hardwareId")?.unwrap_or_default(); - reqwest::Client::new() + provider_http_client() .delete(format!("{}/quadrant/usage/delete", QNT_BASE_URL)) .header("Authorization", api_key) .header("User-Agent", user_agent) .query(&[("hardware_id", hardware_id)]) .send() - .await?; + .await? + .error_for_status()?; Ok(()) } diff --git a/src-tauri/crates/quadrant-host/Cargo.toml b/src-tauri/crates/quadrant-host/Cargo.toml new file mode 100644 index 00000000..d28f6031 --- /dev/null +++ b/src-tauri/crates/quadrant-host/Cargo.toml @@ -0,0 +1,32 @@ +[package] +name = "quadrant-host" +version.workspace = true +edition = "2024" + +[features] +default = [] +curseforge = ["quadrant-core/curseforge"] + +[dependencies] +anyhow = "1.0.102" +colog = "1.4.0" +futures = "0.3.32" +keyring = { version = "3.6.3", features = [ + "apple-native", + "windows-native", + "sync-secret-service", +] } +log = "0.4.29" +quadrant-core = { path = "../quadrant-core" } +serde = { version = "1.0.228", features = ["derive"] } +serde_json = "1.0.149" +tokio = { version = "1.50.0", features = ["full"] } +tokio-tungstenite = { version = "0.29.0", features = [ + "rustls-tls-webpki-roots", + "connect", +] } +url = "2.5.8" +uuid = { version = "1.23.0", features = ["v7"] } + +[dev-dependencies] +tempfile = "3.27.0" diff --git a/src-tauri/crates/quadrant-host/src/lib.rs b/src-tauri/crates/quadrant-host/src/lib.rs new file mode 100644 index 00000000..73a39e7f --- /dev/null +++ b/src-tauri/crates/quadrant-host/src/lib.rs @@ -0,0 +1,2260 @@ +use std::{ + collections::HashMap, + fs, + path::PathBuf, + sync::{Arc, Mutex, Once}, + time::Duration, +}; + +use anyhow::anyhow; +use futures::StreamExt; +use keyring::{Entry, Error as KeyringError}; +use quadrant_core::{ + Error, Result, + account::{ + KEYRING_SERVICE, clear_account_token, + id::{ + AccountInfo, Notification, NotificationCursor, NotificationWsFrame, + get_account_info_with_refresh, get_notification_history_all_since_with_refresh, + oauth2_login, read_notification, + }, + quadrant_settings_sync, + quadrant_share::{ + QuadrantShareSubmissionResponse, get_quadrant_share_modpack, share_modpack_raw, + }, + quadrant_sync::{ + SyncedModpack, answer_invite, delete_synced_modpack, get_synced_modpacks, + invite_member, kick_member, + }, + set_secret, + }, + config::{ensure_default_app_config, get_mc_folder}, + events::BackendEvent, + mc_mod::{ + GetModArgs, GlobalSearchModsArgs, IdentifiedMod, MinecraftVersion, Mod, ModType, + UniversalModFile, check_mod_updates, get_user_agent, get_versions, identify_modpack, + install_mod, install_remote_file, search_mods, + }, + models::{Article, InstalledMod, InstalledModpack, LocalModpack, ModLoader, ModSource}, + modpacks::{ + apply_modpack, create_modpack, delete_mod, delete_modpack, export_modpack_to, get_modpacks, + install_modpack, register_mod, set_modpack_sync_date, update_modpack, + }, + ports::{EventSink, SecretStore, SettingsStore}, + telemetry::{AppInfo, get_telemetry_info, remove_telemetry, send_telemetry}, +}; +use serde::{Deserialize, Deserializer, Serialize, de::DeserializeOwned, de::Error as DeError}; +use serde_json::Value; +use tokio::{ + sync::{Mutex as AsyncMutex, broadcast}, + task::JoinHandle, +}; +use tokio_tungstenite::{ + connect_async, + tungstenite::{Message, client::IntoClientRequest}, +}; +use url::Url; +use uuid::Uuid; + +#[cfg(feature = "curseforge")] +pub use quadrant_core::mc_mod::curseforge::{ + get_mod_curseforge, get_mod_deps_curseforge, get_mod_owners_curseforge, +}; +pub use quadrant_core::mc_mod::modrinth::{ + get_mod_deps_modrinth, get_mod_modrinth, get_mod_owners_modrinth, +}; +pub use quadrant_core::mc_mod::{get_mod_url, get_user_url}; + +const NOTIFICATION_CURSOR_CREATED_AT_KEY: &str = "notificationCursorCreatedAt"; +const NOTIFICATION_CURSOR_NOTIFICATION_ID_KEY: &str = "notificationCursorNotificationId"; +const SETTINGS_SYNC_INTERVAL_SECS: u64 = 120; +const WS_REPLAY_LIMIT: usize = 500; +const REFRESH_SYNCED_MODPACKS_EVENT: &str = "refreshSyncedModpacks"; +static LOGGER_INIT: Once = Once::new(); + +#[derive(Debug, Clone)] +pub struct QuadrantHostOptions { + pub data_dir: PathBuf, + pub mc_folder: Option, + pub api_base_url: Option, + pub oauth_client_id: String, + pub oauth_client_secret: String, + pub quadrant_api_key: String, + pub config_store_name: String, + pub update_store_name: String, + pub keyring_service_name: String, + pub app_version: String, + pub os_name: String, + pub user_agent: String, +} + +impl QuadrantHostOptions { + pub fn new( + data_dir: PathBuf, + oauth_client_id: impl Into, + oauth_client_secret: impl Into, + quadrant_api_key: impl Into, + ) -> Self { + Self { + data_dir, + mc_folder: None, + api_base_url: None, + oauth_client_id: oauth_client_id.into(), + oauth_client_secret: oauth_client_secret.into(), + quadrant_api_key: quadrant_api_key.into(), + config_store_name: "config.json".to_string(), + update_store_name: "updateConfig.json".to_string(), + keyring_service_name: KEYRING_SERVICE.to_string(), + app_version: env!("CARGO_PKG_VERSION").to_string(), + os_name: std::env::consts::OS.to_uppercase(), + user_agent: get_user_agent(), + } + } +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +pub struct HostEventEnvelope { + pub event: String, + pub payload: Value, +} + +#[derive(Clone)] +pub struct QuadrantHost { + inner: Arc, +} + +struct QuadrantHostInner { + options: QuadrantHostOptions, + config_store: JsonFileStore, + _update_store: JsonFileStore, + secret_store: KeyringSecretStore, + event_sink: HostEventBridge, + runtime_state: AsyncMutex, + worker_handles: AsyncMutex, +} + +#[derive(Default)] +struct WorkerHandles { + notification: Option>, + settings_sync: Option>, +} + +#[derive(Default)] +struct HostRuntimeState { + updated_modpacks: Vec, + notification_connection_id: String, + notification_state: NotificationRuntimeState, +} + +#[derive(Clone)] +struct JsonFileStore { + path: PathBuf, + values: Arc>>, +} + +impl JsonFileStore { + fn new(path: PathBuf) -> Result { + let values = Self::read_values_from_disk(&path)?; + Ok(Self { + path, + values: Arc::new(Mutex::new(values)), + }) + } + + fn read_values_from_disk(path: &PathBuf) -> Result> { + if !path.exists() { + return Ok(HashMap::new()); + } + + let raw = fs::read_to_string(path)?; + let parsed = serde_json::from_str::(&raw) + .map_err(|error| anyhow!("failed to parse {}: {error}", path.display()))?; + Ok(parsed + .as_object() + .cloned() + .unwrap_or_default() + .into_iter() + .collect::>()) + } + + fn reload(&self, values: &mut HashMap) -> Result<()> { + match Self::read_values_from_disk(&self.path) { + Ok(updated_values) => *values = updated_values, + Err(error) => { + log::warn!( + "Failed to reload settings store {}: {error}", + self.path.display() + ); + } + } + Ok(()) + } + + fn delete_key(&self, key: &str) -> Result<()> { + let mut values = self + .values + .lock() + .map_err(|_| anyhow!("settings store is busy"))?; + self.reload(&mut values)?; + values.remove(key); + self.persist(&values) + } + + fn persist(&self, values: &HashMap) -> Result<()> { + if let Some(parent) = self.path.parent() { + fs::create_dir_all(parent)?; + } + let temp_path = self.path.with_extension(format!("{}.tmp", Uuid::now_v7())); + fs::write( + &temp_path, + serde_json::to_vec_pretty(&Value::Object(values.clone().into_iter().collect()))?, + )?; + fs::rename(temp_path, &self.path)?; + Ok(()) + } +} + +impl SettingsStore for JsonFileStore { + fn get_value(&self, key: &str) -> Result> { + let mut values = self + .values + .lock() + .map_err(|_| anyhow!("settings store is busy"))?; + self.reload(&mut values)?; + Ok(values.get(key).cloned()) + } + + fn set_value(&self, key: &str, value: Value) -> Result<()> { + let mut values = self + .values + .lock() + .map_err(|_| anyhow!("settings store is busy"))?; + self.reload(&mut values)?; + values.insert(key.to_string(), value); + self.persist(&values) + } + + fn entries(&self) -> Result> { + let mut values = self + .values + .lock() + .map_err(|_| anyhow!("settings store is busy"))?; + self.reload(&mut values)?; + Ok(values + .iter() + .map(|(key, value)| (key.clone(), value.clone())) + .collect()) + } +} + +#[derive(Clone)] +struct KeyringSecretStore { + service_name: String, +} + +impl SecretStore for KeyringSecretStore { + fn get_secret(&self, key: &str) -> Result> { + let entry = Entry::new(&self.service_name, key)?; + map_keyring_secret_result(entry.get_password()) + } + + fn set_secret(&self, key: &str, value: &str) -> Result<()> { + let entry = Entry::new(&self.service_name, key)?; + entry.set_password(value)?; + Ok(()) + } + + fn delete_secret(&self, key: &str) -> Result<()> { + let entry = Entry::new(&self.service_name, key)?; + match entry.delete_credential() { + Ok(()) | Err(KeyringError::NoEntry) => Ok(()), + Err(error) => Err(error.into()), + } + } +} + +fn map_keyring_secret_result( + result: std::result::Result, +) -> Result> { + match result { + Ok(value) => Ok(Some(value)), + Err(KeyringError::NoEntry) => Ok(None), + Err(error) => Err(error.into()), + } +} + +#[derive(Clone)] +struct HostEventBridge { + sender: broadcast::Sender, +} + +impl HostEventBridge { + fn new() -> Self { + let (sender, _) = broadcast::channel(256); + Self { sender } + } + + fn subscribe(&self) -> broadcast::Receiver { + self.sender.subscribe() + } + + fn emit(&self, event: impl Into, payload: T) -> Result<()> { + let _ = self.sender.send(HostEventEnvelope { + event: event.into(), + payload: serde_json::to_value(payload)?, + }); + Ok(()) + } + + fn emit_value(&self, event: impl Into, payload: Value) { + let _ = self.sender.send(HostEventEnvelope { + event: event.into(), + payload, + }); + } +} + +impl EventSink for HostEventBridge { + fn publish(&self, event: BackendEvent) -> Result<()> { + match event { + BackendEvent::ModDownloadProgress(payload) => self.emit("modDownloadProgress", payload), + BackendEvent::ModInstallProgress(payload) => self.emit("modInstallProgress", payload), + BackendEvent::ModpackDownloadProgress(payload) => { + self.emit("modpackDownloadProgress", payload) + } + BackendEvent::QuadrantExportProgress(payload) => { + self.emit("quadrantExportProgress", payload) + } + BackendEvent::QuadrantShareSubmission(payload) => { + self.emit_value("quadrantShareSubmission", payload); + Ok(()) + } + BackendEvent::RefreshNotifications(payload) => { + self.emit_value("refreshNotifications", payload); + Ok(()) + } + BackendEvent::RecheckAccountToken => self.emit("recheckAccountToken", Value::Null), + } + } +} + +impl QuadrantHost { + pub fn new(options: QuadrantHostOptions) -> Result { + LOGGER_INIT.call_once(|| { + colog::init(); + }); + fs::create_dir_all(&options.data_dir)?; + if let Some(api_base_url) = &options.api_base_url { + unsafe { + std::env::set_var("QUADRANT_API_BASE_URL", api_base_url); + } + } + + let config_store = JsonFileStore::new(options.data_dir.join(&options.config_store_name))?; + let update_store = JsonFileStore::new(options.data_dir.join(&options.update_store_name))?; + ensure_default_app_config(&config_store)?; + + if let Some(mc_folder) = &options.mc_folder { + config_store.set_string("mcFolder", mc_folder.to_string_lossy().to_string())?; + } + + Ok(Self { + inner: Arc::new(QuadrantHostInner { + secret_store: KeyringSecretStore { + service_name: options.keyring_service_name.clone(), + }, + event_sink: HostEventBridge::new(), + runtime_state: AsyncMutex::new(HostRuntimeState { + notification_connection_id: Uuid::now_v7().to_string(), + ..HostRuntimeState::default() + }), + worker_handles: AsyncMutex::new(WorkerHandles::default()), + config_store, + _update_store: update_store, + options, + }), + }) + } + + pub fn options(&self) -> &QuadrantHostOptions { + &self.inner.options + } + + pub fn subscribe_events(&self) -> broadcast::Receiver { + self.inner.event_sink.subscribe() + } + + pub async fn start_background_workers(&self) -> Result<()> { + let mut worker_handles = self.inner.worker_handles.lock().await; + + if worker_handles.notification.is_none() { + let host = self.clone(); + worker_handles.notification = Some(tokio::spawn(async move { + host.notification_worker_loop().await; + })); + } + + if worker_handles.settings_sync.is_none() { + let host = self.clone(); + worker_handles.settings_sync = Some(tokio::spawn(async move { + host.settings_sync_loop().await; + })); + } + + Ok(()) + } + + pub async fn stop_background_workers(&self) -> Result<()> { + let mut worker_handles = self.inner.worker_handles.lock().await; + + if let Some(handle) = worker_handles.notification.take() { + handle.abort(); + let _ = handle.await; + } + + if let Some(handle) = worker_handles.settings_sync.take() { + handle.abort(); + let _ = handle.await; + } + + Ok(()) + } + + pub async fn shutdown(&self) -> Result<()> { + self.stop_background_workers().await + } + + pub fn init_config(&self) -> Result<()> { + ensure_default_app_config(&self.inner.config_store)?; + self.ensure_override_mc_folder() + } + + pub fn get_minecraft_folder(&self) -> Result { + self.inner + .config_store + .get_string("mcFolder")? + .map(PathBuf::from) + .or_else(|| get_mc_folder().ok().flatten()) + .ok_or_else(|| anyhow!("mcFolder is not configured")) + } + + pub fn get_modpacks_folder(&self) -> Result { + Ok(self.get_minecraft_folder()?.join("modpacks")) + } + + pub async fn get_modpacks(&self, hide_free: bool) -> Result> { + get_modpacks(&self.get_minecraft_folder()?, hide_free) + } + + pub fn frontend_apply_modpack(&self, name: String) -> Result<()> { + apply_modpack(&self.get_minecraft_folder()?, &name) + } + + pub async fn delete_mod(&self, modpack_name: String, mod_id: String) -> Result<()> { + let modpack = delete_mod( + &self.get_minecraft_folder()?, + &self.get_modpacks(false).await?, + &modpack_name, + &mod_id, + )?; + self.maybe_auto_sync_updated_modpack(Some(modpack)).await + } + + pub async fn update_modpack( + &self, + modpack_source: String, + name: Option, + version: Option, + mod_loader: Option, + ) -> Result<()> { + let modpack = update_modpack( + &self.get_minecraft_folder()?, + &self.get_modpacks(false).await?, + &modpack_source, + name, + version, + mod_loader, + )?; + self.maybe_auto_sync_updated_modpack(Some(modpack)).await + } + + pub async fn create_modpack( + &self, + name: String, + version: String, + mod_loader: ModLoader, + ) -> Result<()> { + create_modpack( + &self.get_minecraft_folder()?, + &self.get_modpacks(false).await?, + &name, + &version, + mod_loader, + ) + } + + pub async fn delete_modpack(&self, name: String) -> Result<()> { + delete_modpack( + &self.get_minecraft_folder()?, + &self.get_modpacks(false).await?, + &name, + ) + } + + pub async fn register_mod(&self, mod_: InstalledMod, modpack: String) -> Result<()> { + register_mod( + &self.get_minecraft_folder()?, + &self.get_modpacks(false).await?, + mod_, + &modpack, + ) + } + + pub async fn install_modpack(&self, mod_config: InstalledModpack) -> Result<()> { + install_modpack( + &self.get_minecraft_folder()?, + mod_config, + &self.inner.config_store, + &self.inner.event_sink, + ) + .await + } + + pub async fn export_modpack_to(&self, modpack: String, destination: PathBuf) -> Result<()> { + export_modpack_to( + &self.get_minecraft_folder()?, + &modpack, + &destination, + &self.inner.event_sink, + ) + } + + pub fn set_modpack_sync_date( + &self, + time: u64, + modpack: String, + modpack_id: Option, + ) -> Result<()> { + set_modpack_sync_date( + &self.get_minecraft_folder()?, + time, + &modpack, + modpack_id.as_deref(), + ) + } + + pub async fn get_versions(&self) -> Result> { + get_versions().await + } + + pub async fn search_mods(&self, args: GlobalSearchModsArgs) -> Result> { + search_mods(args, &self.inner.config_store).await + } + + pub async fn check_mod_updates( + &self, + mod_to_update: Mod, + minecraft_version: String, + mod_loader: ModLoader, + modpack_name: String, + ) -> Result> { + let modpack = self + .get_modpacks(false) + .await? + .into_iter() + .find(|modpack| modpack.name == modpack_name) + .ok_or_else(|| anyhow!("Modpack not found"))?; + let show_unupgradeable_mods = self + .inner + .config_store + .get_bool("showUnupgradeableMods")? + .unwrap_or(false); + + check_mod_updates( + mod_to_update, + minecraft_version, + mod_loader, + modpack, + show_unupgradeable_mods, + ) + .await + } + + pub async fn install_mod( + &self, + id: String, + minecraft_version: String, + mod_loader: ModLoader, + source: ModSource, + modpack: Option, + mod_type: ModType, + file_id: Option, + ) -> Result<()> { + let updated_modpack = install_mod( + &self.get_minecraft_folder()?, + &self.get_modpacks(false).await?, + &self.inner.config_store, + &self.inner.event_sink, + id, + minecraft_version, + mod_loader, + source, + modpack, + mod_type, + file_id, + ) + .await?; + self.maybe_auto_sync_updated_modpack(updated_modpack).await + } + + pub async fn install_remote_file( + &self, + file: UniversalModFile, + mod_type: ModType, + modpack: Option, + source: ModSource, + id: String, + ) -> Result<()> { + let updated_modpack = install_remote_file( + &self.get_minecraft_folder()?, + &self.get_modpacks(false).await?, + &self.inner.event_sink, + file, + mod_type, + modpack, + source, + id, + ) + .await?; + self.maybe_auto_sync_updated_modpack(updated_modpack).await + } + + pub async fn identify_modpack(&self, modpack: String) -> Result> { + let curseforge_enabled = self + .inner + .config_store + .get_bool("curseforge")? + .unwrap_or(false); + let modrinth_enabled = self + .inner + .config_store + .get_bool("modrinth")? + .unwrap_or(false); + identify_modpack( + &self.get_minecraft_folder()?, + modpack, + curseforge_enabled, + modrinth_enabled, + ) + .await + } + + pub async fn get_mod_modrinth(&self, args: GetModArgs) -> Result { + get_mod_modrinth(args).await + } + + pub async fn get_mod_owners_modrinth(&self, id: String) -> Result> { + get_mod_owners_modrinth(id).await + } + + pub async fn get_mod_deps_modrinth(&self, id: String) -> Result> { + get_mod_deps_modrinth(id).await + } + + #[cfg(feature = "curseforge")] + pub async fn get_mod_curseforge(&self, args: GetModArgs) -> Result { + get_mod_curseforge(args).await + } + + #[cfg(feature = "curseforge")] + pub async fn get_mod_owners_curseforge(&self, id: String) -> Result> { + get_mod_owners_curseforge(id).await + } + + #[cfg(feature = "curseforge")] + pub async fn get_mod_deps_curseforge(&self, id: String) -> Result> { + get_mod_deps_curseforge(id).await + } + + pub fn set_secret(&self, key: String, value: String) -> Result<()> { + set_secret(&self.inner.secret_store, &key, &value) + } + + pub fn clear_account_token(&self) -> Result<()> { + clear_account_token(&self.inner.secret_store) + } + + pub fn logout(&self) -> Result<()> { + clear_account_token(&self.inner.secret_store) + } + + pub fn oauth2_client_id(&self) -> String { + self.inner.options.oauth_client_id.clone() + } + + pub async fn get_account_info(&self) -> Result { + get_account_info_with_refresh( + &self.inner.secret_store, + &self.inner.options.user_agent, + &self.inner.options.oauth_client_id, + &self.inner.options.oauth_client_secret, + ) + .await + } + + pub async fn oauth2_login(&self, code: String, redirect_uri: String) -> Result<()> { + oauth2_login( + &self.inner.secret_store, + &self.inner.options.user_agent, + &self.inner.options.oauth_client_id, + &self.inner.options.oauth_client_secret, + code, + redirect_uri, + ) + .await?; + self.inner + .event_sink + .emit("recheckAccountToken", Value::Null) + } + + pub async fn read_notification(&self, notification_id: String) -> Result<()> { + read_notification( + &self.inner.secret_store, + &self.inner.options.user_agent, + notification_id.clone(), + ) + .await?; + + if let Some(notifications) = self + .mark_notification_read_and_snapshot(¬ification_id) + .await + { + self.inner + .event_sink + .emit("refreshNotifications", notifications)?; + } + + Ok(()) + } + + pub async fn get_synced_modpacks( + &self, + show_owners: bool, + modpack_id: Option, + ) -> Result> { + get_synced_modpacks( + &self.inner.secret_store, + &self.inner.options.user_agent, + show_owners, + modpack_id, + ) + .await + } + + pub async fn kick_member(&self, modpack_id: String, username: String) -> Result<()> { + kick_member( + &self.inner.secret_store, + &self.inner.options.user_agent, + modpack_id, + username, + ) + .await + } + + pub async fn invite_member( + &self, + modpack_id: String, + username: String, + admin: bool, + ) -> Result<()> { + invite_member( + &self.inner.secret_store, + &self.inner.options.user_agent, + modpack_id, + username, + admin, + ) + .await + } + + pub async fn delete_synced_modpack(&self, modpack_id: String) -> Result<()> { + delete_synced_modpack( + &self.inner.secret_store, + &self.inner.options.user_agent, + modpack_id, + ) + .await + } + + pub async fn sync_modpack(&self, modpack: LocalModpack, overwrite: bool) -> Result<()> { + let connection_id = { + let runtime = self.inner.runtime_state.lock().await; + runtime.notification_connection_id.clone() + }; + + let timestamp = quadrant_core::account::quadrant_sync::sync_modpack( + &self.inner.secret_store, + &self.inner.options.user_agent, + modpack.clone(), + overwrite, + Some(connection_id.as_str()), + ) + .await?; + + let persisted_modpack_id = self + .resolve_submitted_modpack_id(&modpack, timestamp) + .await? + .or_else(|| modpack.modpack_id.clone()); + self.persist_sync_metadata( + &modpack.name, + timestamp as u64, + persisted_modpack_id.as_deref(), + ) + } + + pub async fn answer_invite( + &self, + modpack_id: String, + notification_id: String, + answer: bool, + ) -> Result<()> { + answer_invite( + &self.inner.secret_store, + &self.inner.options.user_agent, + modpack_id, + answer, + ) + .await?; + self.read_notification(notification_id).await + } + + pub async fn share_modpack( + &self, + modpack_name: String, + ) -> Result { + let modpack = self + .get_modpacks(false) + .await? + .into_iter() + .find(|modpack| modpack.name == modpack_name) + .ok_or_else(|| anyhow!("Modpack not found"))?; + self.share_modpack_raw(modpack.into()).await + } + + pub async fn share_modpack_raw( + &self, + mod_config: InstalledModpack, + ) -> Result { + let response = share_modpack_raw( + &self.inner.config_store, + &self.inner.secret_store, + &self.inner.options.user_agent, + mod_config, + &self.inner.options.quadrant_api_key, + ) + .await?; + self.inner + .event_sink + .emit("quadrantShareSubmission", &response)?; + self.send_telemetry().await?; + Ok(response) + } + + pub async fn get_quadrant_share_modpack(&self, code: String) -> Result { + get_quadrant_share_modpack( + &self.inner.options.user_agent, + &self.inner.options.quadrant_api_key, + code, + ) + .await + } + + pub async fn get_quadrant_settings(&self) -> Result<()> { + quadrant_settings_sync::get_quadrant_settings( + &self.inner.config_store, + &self.inner.secret_store, + &self.inner.options.user_agent, + ) + .await + } + + pub async fn submit_quadrant_settings(&self) -> Result<()> { + quadrant_settings_sync::submit_quadrant_settings( + &self.inner.config_store, + &self.inner.secret_store, + &self.inner.options.user_agent, + ) + .await + } + + pub async fn get_news(&self) -> Result> { + quadrant_core::rss::get_news().await + } + + pub async fn get_telemetry_info(&self) -> Result { + get_telemetry_info( + &self.inner.config_store, + self.inner.options.app_version.clone(), + self.inner.options.os_name.clone(), + ) + .await + } + + pub async fn send_telemetry(&self) -> Result<()> { + send_telemetry( + &self.inner.config_store, + &self.inner.options.user_agent, + self.inner.options.app_version.clone(), + self.inner.options.os_name.clone(), + &self.inner.options.quadrant_api_key, + ) + .await + } + + pub async fn remove_telemetry(&self) -> Result<()> { + remove_telemetry( + &self.inner.config_store, + &self.inner.options.user_agent, + &self.inner.options.quadrant_api_key, + ) + .await + } + + pub fn supported_commands() -> &'static [&'static str] { + &[ + "get_modpacks", + "frontend_apply_modpack", + "delete_mod", + "update_modpack", + "create_modpack", + "delete_modpack", + "register_mod", + "install_modpack", + "export_modpack_to", + "set_modpack_sync_date", + "get_news", + "get_minecraft_folder", + "get_modpacks_folder", + "init_config", + "search_mods", + "get_versions", + "get_user_url", + "install_mod", + "install_remote_file", + "identify_modpack", + "check_mod_updates", + "get_mod_modrinth", + "get_mod_owners_modrinth", + "get_mod_deps_modrinth", + "set_secret", + "clear_account_token", + "get_account_info", + "oauth2_login", + "oauth2_client_id", + "read_notification", + "get_synced_modpacks", + "kick_member", + "invite_member", + "delete_synced_modpack", + "sync_modpack", + "answer_invite", + "share_modpack", + "share_modpack_raw", + "get_quadrant_share_modpack", + "get_quadrant_settings", + "submit_quadrant_settings", + "send_telemetry", + "remove_telemetry", + #[cfg(feature = "curseforge")] + "get_mod_curseforge", + #[cfg(feature = "curseforge")] + "get_mod_owners_curseforge", + #[cfg(feature = "curseforge")] + "get_mod_deps_curseforge", + ] + } + + #[allow(clippy::too_many_lines)] + pub async fn invoke(&self, command: &str, payload: Value) -> Result { + match command { + "get_modpacks" => { + let args: HideFreeArgs = from_value(payload)?; + to_value(self.get_modpacks(args.hide_free).await?) + } + "frontend_apply_modpack" => { + let args: NameArgs = from_value(payload)?; + self.frontend_apply_modpack(args.name)?; + Ok(Value::Null) + } + "delete_mod" => { + let args: DeleteModArgs = from_value(payload)?; + self.delete_mod(args.modpack_name, args.mod_id).await?; + Ok(Value::Null) + } + "update_modpack" => { + let args: UpdateModpackArgs = from_value(payload)?; + self.update_modpack( + args.modpack_source, + args.name, + args.version, + args.mod_loader, + ) + .await?; + Ok(Value::Null) + } + "create_modpack" => { + let args: CreateModpackArgs = from_value(payload)?; + self.create_modpack(args.name, args.version, args.mod_loader) + .await?; + Ok(Value::Null) + } + "delete_modpack" => { + let args: NameArgs = from_value(payload)?; + self.delete_modpack(args.name).await?; + Ok(Value::Null) + } + "register_mod" => { + let args: RegisterModArgs = from_value(payload)?; + self.register_mod(args.mod_, args.modpack).await?; + Ok(Value::Null) + } + "install_modpack" => { + let args: InstallModpackArgs = from_value(payload)?; + self.install_modpack(args.mod_config).await?; + Ok(Value::Null) + } + "export_modpack_to" => { + let args: ExportModpackArgs = from_value(payload)?; + self.export_modpack_to(args.modpack, PathBuf::from(args.destination)) + .await?; + Ok(Value::Null) + } + "set_modpack_sync_date" => { + let args: SetModpackSyncDateArgs = from_value(payload)?; + self.set_modpack_sync_date(args.time, args.modpack, args.modpack_id)?; + Ok(Value::Null) + } + "get_news" => to_value(self.get_news().await?), + "get_minecraft_folder" => to_value(self.get_minecraft_folder()?), + "get_modpacks_folder" => to_value(self.get_modpacks_folder()?), + "init_config" => { + self.init_config()?; + Ok(Value::Null) + } + "search_mods" => { + let args: SearchModsCompatArgs = from_value(payload)?; + to_value(self.search_mods(args.args).await?) + } + "get_versions" => to_value(self.get_versions().await?), + "get_user_url" => { + let args: GetUserUrlArgs = from_value(payload)?; + to_value(get_user_url(args.username, args.source)) + } + "install_mod" => { + let args: InstallModArgs = from_value(payload)?; + self.install_mod( + args.id, + args.minecraft_version, + args.mod_loader, + args.source, + args.modpack, + args.mod_type, + args.file_id, + ) + .await?; + Ok(Value::Null) + } + "install_remote_file" => { + let args: InstallRemoteFileArgs = from_value(payload)?; + self.install_remote_file( + args.file, + args.mod_type, + args.modpack, + args.source, + args.id, + ) + .await?; + Ok(Value::Null) + } + "identify_modpack" => { + let args: ModpackOnlyArgs = from_value(payload)?; + to_value(self.identify_modpack(args.modpack).await?) + } + "check_mod_updates" => { + let args: CheckModUpdatesArgs = from_value(payload)?; + to_value( + self.check_mod_updates( + args.mod_to_update, + args.minecraft_version, + args.mod_loader, + args.modpack_name, + ) + .await?, + ) + } + "get_mod_modrinth" => { + let args: GetModArgsWrapper = from_value(payload)?; + to_value(self.get_mod_modrinth(args.args).await?) + } + "get_mod_owners_modrinth" => { + let args: IdArgs = from_value(payload)?; + to_value(self.get_mod_owners_modrinth(args.id).await?) + } + "get_mod_deps_modrinth" => { + let args: IdArgs = from_value(payload)?; + to_value(self.get_mod_deps_modrinth(args.id).await?) + } + #[cfg(feature = "curseforge")] + "get_mod_curseforge" => { + let args: GetModArgsWrapper = from_value(payload)?; + to_value(self.get_mod_curseforge(args.args).await?) + } + #[cfg(feature = "curseforge")] + "get_mod_owners_curseforge" => { + let args: IdArgs = from_value(payload)?; + to_value(self.get_mod_owners_curseforge(args.id).await?) + } + #[cfg(feature = "curseforge")] + "get_mod_deps_curseforge" => { + let args: IdArgs = from_value(payload)?; + to_value(self.get_mod_deps_curseforge(args.id).await?) + } + "set_secret" => { + let args: SetSecretArgs = from_value(payload)?; + self.set_secret(args.key, args.value)?; + Ok(Value::Null) + } + "clear_account_token" => { + self.clear_account_token()?; + Ok(Value::Null) + } + "get_account_info" => to_value(self.get_account_info().await?), + "oauth2_login" => { + let args: OAuthLoginArgs = from_value(payload)?; + self.oauth2_login(args.code, args.redirect_uri).await?; + Ok(Value::Null) + } + "oauth2_client_id" => to_value(self.oauth2_client_id()), + "read_notification" => { + let args: NotificationIdArgs = from_value(payload)?; + self.read_notification(args.notification_id).await?; + Ok(Value::Null) + } + "get_synced_modpacks" => { + let args: GetSyncedModpacksArgs = from_value(payload)?; + to_value( + self.get_synced_modpacks(args.show_owners, args.modpack_id) + .await?, + ) + } + "kick_member" => { + let args: KickMemberArgs = from_value(payload)?; + self.kick_member(args.modpack_id, args.username).await?; + Ok(Value::Null) + } + "invite_member" => { + let args: InviteMemberArgs = from_value(payload)?; + self.invite_member(args.modpack_id, args.username, args.admin) + .await?; + Ok(Value::Null) + } + "delete_synced_modpack" => { + let args: ModpackIdArgs = from_value(payload)?; + self.delete_synced_modpack(args.modpack_id).await?; + Ok(Value::Null) + } + "sync_modpack" => { + let args: SyncModpackArgs = from_value(payload)?; + self.sync_modpack(args.modpack, args.overwrite).await?; + Ok(Value::Null) + } + "answer_invite" => { + let args: AnswerInviteArgs = from_value(payload)?; + self.answer_invite(args.modpack_id, args.notification_id, args.answer) + .await?; + Ok(Value::Null) + } + "share_modpack" => { + let args: ModpackNameArgs = from_value(payload)?; + to_value(self.share_modpack(args.modpack_name).await?) + } + "share_modpack_raw" => { + let args: InstallModpackArgs = from_value(payload)?; + to_value(self.share_modpack_raw(args.mod_config).await?) + } + "get_quadrant_share_modpack" => { + let args: CodeArgs = from_value(payload)?; + to_value(self.get_quadrant_share_modpack(args.code).await?) + } + "get_quadrant_settings" => { + self.get_quadrant_settings().await?; + Ok(Value::Null) + } + "submit_quadrant_settings" => { + self.submit_quadrant_settings().await?; + Ok(Value::Null) + } + "send_telemetry" => { + self.send_telemetry().await?; + Ok(Value::Null) + } + "remove_telemetry" => { + self.remove_telemetry().await?; + Ok(Value::Null) + } + _ => Err(anyhow!("Unsupported command: {command}")), + } + } + + fn ensure_override_mc_folder(&self) -> Result<()> { + if let Some(mc_folder) = &self.inner.options.mc_folder { + self.inner + .config_store + .set_string("mcFolder", mc_folder.to_string_lossy().to_string())?; + } + Ok(()) + } + + async fn maybe_auto_sync_updated_modpack( + &self, + updated_modpack: Option, + ) -> Result<()> { + let Some(modpack) = updated_modpack else { + return Ok(()); + }; + + let auto_sync = self + .inner + .config_store + .get_bool("autoQuadrantSync")? + .unwrap_or(false); + + if modpack.last_synced != 0 && auto_sync { + self.sync_modpack(modpack, true).await?; + } + + Ok(()) + } + + async fn resolve_submitted_modpack_id( + &self, + modpack: &LocalModpack, + timestamp: i64, + ) -> Result> { + let synced_modpacks = self.get_synced_modpacks(false, None).await?; + + let mut matching = synced_modpacks.into_iter().filter(|synced_modpack| { + synced_modpack.name == modpack.name + && synced_modpack.minecraft_version == modpack.version + && synced_modpack.mod_loader == modpack.mod_loader + && synced_modpack.last_synced == timestamp + }); + + let first = matching.next().map(|modpack| modpack.modpack_id); + if matching.next().is_some() { + return Ok(None); + } + + Ok(first) + } + + fn persist_sync_metadata( + &self, + modpack_name: &str, + last_synced: u64, + modpack_id: Option<&str>, + ) -> Result<()> { + let modpack_folder = self.get_modpacks_folder()?.join(modpack_name); + if !modpack_folder.exists() { + return Ok(()); + } + + set_modpack_sync_date( + &self.get_minecraft_folder()?, + last_synced, + modpack_name, + modpack_id, + ) + } + + async fn notification_worker_loop(&self) { + loop { + if let Err(error) = self.bootstrap_notifications().await { + log::error!("Notification bootstrap failed: {error}"); + } + + if let Err(error) = self.run_notification_socket().await { + log::warn!("Notification socket cycle ended: {error}"); + } + + if let Err(error) = self.bootstrap_notifications().await { + log::warn!("Notification catch-up failed after socket cycle: {error}"); + } + + let delay = { + let mut state = self.inner.runtime_state.lock().await; + state.notification_state.reconnect_attempt = + state.notification_state.reconnect_attempt.saturating_add(1); + reconnect_delay(state.notification_state.reconnect_attempt) + }; + tokio::time::sleep(delay).await; + } + } + + async fn settings_sync_loop(&self) { + let mut interval = tokio::time::interval(Duration::from_secs(SETTINGS_SYNC_INTERVAL_SECS)); + loop { + interval.tick().await; + if let Err(error) = self.sync_remote_settings().await { + log::warn!("Settings sync worker failed: {error}"); + } + } + } + + async fn bootstrap_notifications(&self) -> Result<()> { + let cursor = self.load_notification_cursor()?; + let (notifications, next_cursor) = get_notification_history_all_since_with_refresh( + &self.inner.secret_store, + &self.inner.options.user_agent, + Some(&cursor), + None, + true, + &self.inner.options.oauth_client_id, + &self.inner.options.oauth_client_secret, + ) + .await?; + + if notifications.is_empty() && next_cursor == cursor { + return Ok(()); + } + + let merge_outcome = { + let mut state = self.inner.runtime_state.lock().await; + let runtime = &mut state.notification_state; + let merge_outcome = merge_notifications_and_collect_updates(runtime, notifications); + if runtime.cursor != next_cursor { + runtime.cursor = next_cursor.clone(); + } + merge_outcome + }; + + self.persist_notification_cursor(&next_cursor)?; + + if let Some(notifications) = merge_outcome.notifications_for_ui { + self.inner + .event_sink + .emit("refreshNotifications", notifications)?; + } + + for notification in merge_outcome.modpack_sync_notifications { + if let Err(error) = self.handle_modpack_sync_notification(¬ification).await { + log::warn!("Failed to process bootstrapped modpack sync notification: {error}"); + } else { + self.mark_modpack_sync_processed(¬ification).await; + } + } + + self.set_reconnect_attempt(0).await; + Ok(()) + } + + async fn run_notification_socket(&self) -> Result<()> { + let token = quadrant_core::account::get_account_token(&self.inner.secret_store)?; + let (cursor, connection_id) = { + let state = self.inner.runtime_state.lock().await; + ( + state.notification_state.cursor.clone(), + state.notification_connection_id.clone(), + ) + }; + + let ws_url = self.build_notification_ws_url(&cursor, &connection_id)?; + let mut request = ws_url.as_str().into_client_request()?; + request + .headers_mut() + .insert("Authorization", format!("Bearer {token}").parse()?); + request + .headers_mut() + .insert("User-Agent", self.inner.options.user_agent.parse()?); + + let (mut socket, _) = connect_async(request).await?; + self.set_reconnect_attempt(0).await; + + while let Some(frame) = socket.next().await { + match frame? { + Message::Text(payload) => self.handle_notification_ws_frame(&payload).await?, + Message::Close(frame) => { + let reason = frame + .map(|frame| frame.reason.to_string()) + .unwrap_or_else(|| "closed".to_string()); + return Err(anyhow!("notification websocket closed: {reason}")); + } + Message::Ping(_) | Message::Pong(_) | Message::Binary(_) | Message::Frame(_) => {} + } + } + + Err(anyhow!("notification websocket stream ended")) + } + + async fn handle_notification_ws_frame(&self, payload: &str) -> Result<()> { + match serde_json::from_str::(payload)? { + NotificationWsFrame::Connected { server_time } => { + log::info!("Notification websocket connected at {server_time}"); + } + NotificationWsFrame::Notification { + delivery: _, + notification, + } => { + let notifications_to_emit = { + let mut state = self.inner.runtime_state.lock().await; + let outcome = state.notification_state.upsert(notification.clone()); + outcome + .changed + .then(|| state.notification_state.notifications_for_ui()) + }; + + if let Some(notifications) = notifications_to_emit { + self.inner + .event_sink + .emit("refreshNotifications", notifications)?; + } + + if is_modpack_sync_notification(¬ification) { + if let Err(error) = self.handle_modpack_sync_notification(¬ification).await { + log::warn!("Failed to process modpack sync notification: {error}"); + } else { + self.mark_modpack_sync_processed(¬ification).await; + } + } + } + NotificationWsFrame::ReplayComplete { truncated, .. } => { + if truncated { + self.bootstrap_notifications().await?; + } + } + NotificationWsFrame::Error { error } => { + if error.to_lowercase().contains("lagged") { + self.bootstrap_notifications().await?; + } + return Err(anyhow!("notification websocket error frame: {error}")); + } + } + + Ok(()) + } + + fn build_notification_ws_url( + &self, + cursor: &NotificationCursor, + connection_id: &str, + ) -> Result { + let mut url = Url::parse( + self.inner + .options + .api_base_url + .as_deref() + .unwrap_or(quadrant_core::account::QNT_BASE_URL), + )?; + match url.scheme() { + "https" => url + .set_scheme("wss") + .map_err(|_| anyhow!("invalid ws scheme"))?, + "http" => url + .set_scheme("ws") + .map_err(|_| anyhow!("invalid ws scheme"))?, + "wss" | "ws" => {} + _ => return Err(anyhow!("unsupported backend scheme")), + } + url.set_path("/api/v3/account/notifications/ws"); + { + let mut query = url.query_pairs_mut(); + if let Some(created_at) = cursor.created_at.as_deref() { + query.append_pair("since", created_at); + } + query.append_pair("replay_limit", &WS_REPLAY_LIMIT.to_string()); + query.append_pair("modpack_sync", "true"); + query.append_pair("connection_id", connection_id); + } + Ok(url) + } + + async fn handle_modpack_sync_notification(&self, notification: &Notification) -> Result<()> { + let synced_modpack = self + .resolve_synced_modpack_from_notification(notification) + .await?; + let auto_quadrant_sync = self + .inner + .config_store + .get_bool("autoQuadrantSync")? + .unwrap_or(false); + + if auto_quadrant_sync { + let local_modpack = self.resolve_local_modpack_for_sync(&synced_modpack).await?; + let local_modpack = self + .maybe_backfill_local_modpack_id(local_modpack, &synced_modpack) + .await?; + + if let Some(local_modpack) = local_modpack { + self.maybe_apply_remote_modpack_update(&local_modpack, &synced_modpack) + .await?; + } + } + + self.inner + .event_sink + .emit(REFRESH_SYNCED_MODPACKS_EVENT, synced_modpack.modpack_id)?; + Ok(()) + } + + async fn resolve_synced_modpack_from_notification( + &self, + notification: &Notification, + ) -> Result { + if let Some(payload) = parse_notification_message(notification) { + return Ok(payload.modpack); + } + + let resource_id = notification + .resource_id + .as_deref() + .ok_or_else(|| anyhow!("modpack sync notification missing resource_id"))?; + let mut synced_modpacks = self + .get_synced_modpacks(false, Some(resource_id.to_string())) + .await?; + synced_modpacks + .drain(..) + .next() + .ok_or_else(|| anyhow!("modpack sync fallback fetch returned no modpack")) + } + + async fn resolve_local_modpack_for_sync( + &self, + synced_modpack: &SyncedModpack, + ) -> Result> { + let modpacks = get_modpacks(&self.get_minecraft_folder()?, true)?; + + if let Some(local_modpack) = modpacks + .iter() + .find(|modpack| { + modpack.modpack_id.as_deref() == Some(synced_modpack.modpack_id.as_str()) + }) + .cloned() + { + return Ok(Some(local_modpack)); + } + + Ok(modpacks + .into_iter() + .find(|modpack| modpack.last_synced != 0 && modpack.name == synced_modpack.name)) + } + + async fn maybe_backfill_local_modpack_id( + &self, + local_modpack: Option, + synced_modpack: &SyncedModpack, + ) -> Result> { + let Some(mut local_modpack) = local_modpack else { + return Ok(None); + }; + + if local_modpack.modpack_id.as_deref() == Some(synced_modpack.modpack_id.as_str()) { + return Ok(Some(local_modpack)); + } + + let local_sync_time = u64::try_from(local_modpack.last_synced / 1000).unwrap_or_default(); + self.persist_sync_metadata( + &local_modpack.name, + local_sync_time, + Some(synced_modpack.modpack_id.as_str()), + )?; + local_modpack.modpack_id = Some(synced_modpack.modpack_id.clone()); + Ok(Some(local_modpack)) + } + + async fn maybe_apply_remote_modpack_update( + &self, + local_modpack: &LocalModpack, + synced_modpack: &SyncedModpack, + ) -> Result<()> { + let local_sync_time = local_modpack.last_synced / 1000; + if synced_modpack.last_synced <= local_sync_time { + return Ok(()); + } + + if !self.begin_modpack_update(&local_modpack.name).await { + return Ok(()); + } + + let install_result = self + .install_modpack(InstalledModpack { + mod_loader: synced_modpack.mod_loader, + name: synced_modpack.name.clone(), + version: synced_modpack.minecraft_version.clone(), + mods: serde_json::from_str(&synced_modpack.mods)?, + }) + .await; + + if let Err(error) = install_result { + self.finish_modpack_update(&local_modpack.name).await; + return Err(error); + } + + self.persist_sync_metadata( + &local_modpack.name, + synced_modpack.last_synced as u64, + Some(synced_modpack.modpack_id.as_str()), + )?; + self.finish_modpack_update(&local_modpack.name).await; + Ok(()) + } + + async fn begin_modpack_update(&self, modpack_name: &str) -> bool { + let mut state = self.inner.runtime_state.lock().await; + if state + .updated_modpacks + .iter() + .any(|name| name == modpack_name) + { + return false; + } + state.updated_modpacks.push(modpack_name.to_string()); + true + } + + async fn finish_modpack_update(&self, modpack_name: &str) { + let mut state = self.inner.runtime_state.lock().await; + state.updated_modpacks.retain(|name| name != modpack_name); + } + + async fn sync_remote_settings(&self) -> Result<()> { + if !self + .inner + .config_store + .get_bool("syncSettings")? + .unwrap_or(false) + { + return Ok(()); + } + + if let Err(error) = self.get_quadrant_settings().await { + if error.to_string() == "Current settings are newer" { + self.submit_quadrant_settings().await?; + } else { + return Err(error); + } + } + + Ok(()) + } + + async fn mark_notification_read_and_snapshot( + &self, + notification_id: &str, + ) -> Option> { + let mut state = self.inner.runtime_state.lock().await; + state + .notification_state + .mark_read(notification_id) + .then(|| state.notification_state.notifications_for_ui()) + } + + async fn set_reconnect_attempt(&self, attempt: u32) { + let mut state = self.inner.runtime_state.lock().await; + state.notification_state.reconnect_attempt = attempt; + } + + async fn mark_modpack_sync_processed(&self, notification: &Notification) { + let mut state = self.inner.runtime_state.lock().await; + state + .notification_state + .mark_modpack_sync_processed(notification); + } + + fn load_notification_cursor(&self) -> Result { + Ok(NotificationCursor { + created_at: self + .inner + .config_store + .get_string(NOTIFICATION_CURSOR_CREATED_AT_KEY)?, + notification_id: self + .inner + .config_store + .get_string(NOTIFICATION_CURSOR_NOTIFICATION_ID_KEY)?, + }) + } + + fn persist_notification_cursor(&self, cursor: &NotificationCursor) -> Result<()> { + match &cursor.created_at { + Some(created_at) => self + .inner + .config_store + .set_string(NOTIFICATION_CURSOR_CREATED_AT_KEY, created_at.clone())?, + None => self + .inner + .config_store + .delete_key(NOTIFICATION_CURSOR_CREATED_AT_KEY)?, + } + match &cursor.notification_id { + Some(notification_id) => self.inner.config_store.set_string( + NOTIFICATION_CURSOR_NOTIFICATION_ID_KEY, + notification_id.clone(), + )?, + None => self + .inner + .config_store + .delete_key(NOTIFICATION_CURSOR_NOTIFICATION_ID_KEY)?, + } + Ok(()) + } +} + +#[derive(Default, Clone)] +struct NotificationRuntimeState { + by_key: HashMap, + key_by_notification_id: HashMap, + ordered_keys: Vec, + processed_modpack_sync_by_key: HashMap, + cursor: NotificationCursor, + reconnect_attempt: u32, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +struct UpsertOutcome { + changed: bool, +} + +struct MergeNotificationsOutcome { + notifications_for_ui: Option>, + modpack_sync_notifications: Vec, +} + +#[derive(Debug, Deserialize)] +struct ModpackSyncPayload { + notification_type: String, + modpack: SyncedModpack, +} + +impl NotificationRuntimeState { + fn upsert(&mut self, notification: Notification) -> UpsertOutcome { + self.advance_cursor(¬ification); + let identity_key = notification_identity_key(¬ification); + match self.by_key.get(&identity_key) { + Some(existing) if existing == ¬ification => UpsertOutcome { changed: false }, + Some(_) => { + if let Some(previous) = self + .by_key + .insert(identity_key.clone(), notification.clone()) + { + self.key_by_notification_id + .remove(previous.notification_id.as_str()); + } + self.key_by_notification_id + .insert(notification.notification_id.clone(), identity_key); + self.sort_keys(); + UpsertOutcome { changed: true } + } + None => { + self.ordered_keys.push(identity_key.clone()); + self.by_key + .insert(identity_key.clone(), notification.clone()); + self.key_by_notification_id + .insert(notification.notification_id.clone(), identity_key); + self.sort_keys(); + UpsertOutcome { changed: true } + } + } + } + + fn mark_read(&mut self, notification_id: &str) -> bool { + let Some(identity_key) = self.key_by_notification_id.get(notification_id).cloned() else { + return false; + }; + let Some(notification) = self.by_key.get_mut(identity_key.as_str()) else { + return false; + }; + if notification.read { + return false; + } + notification.read = true; + true + } + + fn notifications_for_ui(&self) -> Vec { + let mut notifications = self + .ordered_keys + .iter() + .filter_map(|key| self.by_key.get(key)) + .cloned() + .collect::>(); + notifications.sort_by(|a, b| { + b.created_at_unix + .cmp(&a.created_at_unix) + .then_with(|| b.notification_id.cmp(&a.notification_id)) + }); + notifications + } + + fn advance_cursor(&mut self, notification: &Notification) { + let current_created_at = self.cursor.created_at.as_deref(); + let current_notification_id = self.cursor.notification_id.as_deref(); + let is_newer = match current_created_at { + None => true, + Some(created_at) => { + notification.created_at.as_str() > created_at + || (notification.created_at.as_str() == created_at + && current_notification_id + .map(|id| notification.notification_id.as_str() > id) + .unwrap_or(true)) + } + }; + + if is_newer { + self.cursor = NotificationCursor { + created_at: Some(notification.created_at.clone()), + notification_id: Some(notification.notification_id.clone()), + }; + } + } + + fn sort_keys(&mut self) { + self.ordered_keys.sort_by(|left, right| { + let left_notification = self.by_key.get(left).expect("missing notification"); + let right_notification = self.by_key.get(right).expect("missing notification"); + left_notification + .created_at_unix + .cmp(&right_notification.created_at_unix) + .then_with(|| { + left_notification + .notification_id + .cmp(&right_notification.notification_id) + }) + }); + } + + fn should_process_modpack_sync_notification(&self, notification: &Notification) -> bool { + if !is_modpack_sync_notification(notification) { + return false; + } + + let identity_key = notification_identity_key(notification); + self.processed_modpack_sync_by_key + .get(identity_key.as_str()) + .map(|notification_id| notification_id != ¬ification.notification_id) + .unwrap_or(true) + } + + fn mark_modpack_sync_processed(&mut self, notification: &Notification) { + if !is_modpack_sync_notification(notification) { + return; + } + + let identity_key = notification_identity_key(notification); + self.processed_modpack_sync_by_key + .insert(identity_key, notification.notification_id.clone()); + } +} + +fn merge_notifications_and_collect_updates( + runtime: &mut NotificationRuntimeState, + mut notifications: Vec, +) -> MergeNotificationsOutcome { + notifications.sort_by(|left, right| { + left.created_at_unix + .cmp(&right.created_at_unix) + .then_with(|| left.notification_id.cmp(&right.notification_id)) + }); + + let mut changed = false; + let mut latest_modpack_sync_by_key = HashMap::new(); + let mut modpack_sync_order = Vec::new(); + + for notification in notifications { + let should_queue_modpack_sync = + runtime.should_process_modpack_sync_notification(¬ification); + let outcome = runtime.upsert(notification.clone()); + changed |= outcome.changed; + + if should_queue_modpack_sync { + let identity_key = notification_identity_key(¬ification); + latest_modpack_sync_by_key.insert(identity_key.clone(), notification); + if let Some(index) = modpack_sync_order + .iter() + .position(|existing| existing == &identity_key) + { + modpack_sync_order.remove(index); + } + modpack_sync_order.push(identity_key); + } + } + + MergeNotificationsOutcome { + notifications_for_ui: changed.then(|| runtime.notifications_for_ui()), + modpack_sync_notifications: modpack_sync_order + .into_iter() + .filter_map(|identity_key| latest_modpack_sync_by_key.remove(&identity_key)) + .collect(), + } +} + +fn reconnect_delay(attempt: u32) -> Duration { + let secs = match attempt { + 0 | 1 => 1, + 2 => 2, + 3 => 5, + 4 => 10, + 5 => 30, + _ => 60, + }; + Duration::from_secs(secs) +} + +fn parse_notification_message(notification: &Notification) -> Option { + serde_json::from_str::(¬ification.message) + .ok() + .filter(|payload| payload.notification_type == "modpack_sync") +} + +fn is_modpack_sync_notification(notification: &Notification) -> bool { + notification.notification_type.as_deref() == Some("modpack_sync") +} + +fn notification_identity_key(notification: &Notification) -> String { + if is_modpack_sync_notification(notification) + && let Some(resource_id) = notification.resource_id.as_deref() + { + return format!("modpack_sync:{resource_id}"); + } + notification.notification_id.clone() +} + +fn from_value(value: Value) -> Result { + serde_json::from_value(value).map_err(Error::from) +} + +fn to_value(value: T) -> Result { + serde_json::to_value(value).map_err(Error::from) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +struct HideFreeArgs { + hide_free: bool, +} + +#[derive(Deserialize)] +struct NameArgs { + name: String, +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +struct DeleteModArgs { + modpack_name: String, + mod_id: String, +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +struct UpdateModpackArgs { + modpack_source: String, + name: Option, + version: Option, + mod_loader: Option, +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +struct CreateModpackArgs { + name: String, + version: String, + mod_loader: ModLoader, +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +struct RegisterModArgs { + #[serde(alias = "mod")] + mod_: InstalledMod, + modpack: String, +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +struct InstallModpackArgs { + mod_config: InstalledModpack, +} + +#[derive(Deserialize)] +struct ExportModpackArgs { + modpack: String, + destination: String, +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +struct SetModpackSyncDateArgs { + #[serde(deserialize_with = "deserialize_integral_u64")] + time: u64, + modpack: String, + modpack_id: Option, +} + +#[derive(Deserialize)] +struct SearchModsCompatArgs { + args: GlobalSearchModsArgs, +} + +#[derive(Deserialize)] +struct GetUserUrlArgs { + username: String, + source: ModSource, +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +struct InstallModArgs { + id: String, + minecraft_version: String, + mod_loader: ModLoader, + source: ModSource, + modpack: Option, + mod_type: ModType, + file_id: Option, +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +struct InstallRemoteFileArgs { + file: UniversalModFile, + mod_type: ModType, + modpack: Option, + source: ModSource, + id: String, +} + +#[derive(Deserialize)] +struct ModpackOnlyArgs { + modpack: String, +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +struct CheckModUpdatesArgs { + mod_to_update: Mod, + minecraft_version: String, + mod_loader: ModLoader, + modpack_name: String, +} + +#[derive(Deserialize)] +struct GetModArgsWrapper { + args: GetModArgs, +} + +#[derive(Deserialize)] +struct IdArgs { + id: String, +} + +#[derive(Deserialize)] +struct SetSecretArgs { + key: String, + value: String, +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +struct OAuthLoginArgs { + code: String, + redirect_uri: String, +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +struct NotificationIdArgs { + notification_id: String, +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +struct GetSyncedModpacksArgs { + show_owners: bool, + modpack_id: Option, +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +struct KickMemberArgs { + modpack_id: String, + username: String, +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +struct InviteMemberArgs { + modpack_id: String, + username: String, + admin: bool, +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +struct ModpackIdArgs { + modpack_id: String, +} + +#[derive(Deserialize)] +struct SyncModpackArgs { + modpack: LocalModpack, + overwrite: bool, +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +struct AnswerInviteArgs { + modpack_id: String, + notification_id: String, + answer: bool, +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +struct ModpackNameArgs { + modpack_name: String, +} + +fn deserialize_integral_u64<'de, D>(deserializer: D) -> std::result::Result +where + D: Deserializer<'de>, +{ + let value = Value::deserialize(deserializer)?; + match value { + Value::Number(number) => { + if let Some(value) = number.as_u64() { + return Ok(value); + } + if let Some(value) = number.as_i64() { + return u64::try_from(value) + .map_err(|_| D::Error::custom("integer is out of range for u64")); + } + if let Some(value) = number.as_f64() + && value.is_finite() + && value.fract() == 0.0 + && value >= 0.0 + && value <= u64::MAX as f64 + { + return Ok(value as u64); + } + Err(D::Error::custom("expected an integer-valued number")) + } + other => Err(D::Error::custom(format!( + "expected a number for integer deserialization, got {other}" + ))), + } +} + +#[derive(Deserialize)] +struct CodeArgs { + code: String, +} + +#[cfg(test)] +mod tests { + use super::{ + HostEventEnvelope, JsonFileStore, Notification, NotificationCursor, + NotificationRuntimeState, QuadrantHost, QuadrantHostOptions, + merge_notifications_and_collect_updates, + }; + use quadrant_core::{account::KEYRING_SERVICE, ports::SettingsStore}; + use serde_json::Value; + use std::path::PathBuf; + use tempfile::tempdir; + + fn notification(id: &str, unix: i64, read: bool) -> Notification { + Notification { + notification_id: id.to_string(), + user_id: "u1".to_string(), + notification_type: Some("invite_to_sync".to_string()), + resource_id: None, + message: "{\"simple_message\":\"hello\"}".to_string(), + created_at: format!("2026-03-20T10:{unix:02}:00Z"), + created_at_unix: unix, + read, + } + } + + #[test] + fn json_store_persists_updates() { + let temp_dir = tempdir().unwrap(); + let store = JsonFileStore::new(temp_dir.path().join("config.json")).unwrap(); + store.set_bool("modrinth", true).unwrap(); + let reloaded = JsonFileStore::new(temp_dir.path().join("config.json")).unwrap(); + assert_eq!(reloaded.get_bool("modrinth").unwrap(), Some(true)); + } + + #[test] + fn notification_merge_deduplicates_and_orders() { + let mut state = NotificationRuntimeState::default(); + assert!( + merge_notifications_and_collect_updates( + &mut state, + vec![notification("n1", 1, false), notification("n2", 2, false)] + ) + .notifications_for_ui + .is_some() + ); + assert!( + merge_notifications_and_collect_updates(&mut state, vec![notification("n1", 1, false)]) + .notifications_for_ui + .is_none() + ); + + let notifications = state.notifications_for_ui(); + assert_eq!(notifications[0].notification_id, "n2"); + assert_eq!( + state.cursor, + NotificationCursor { + created_at: Some("2026-03-20T10:02:00Z".to_string()), + notification_id: Some("n2".to_string()), + } + ); + } + + #[test] + fn supported_commands_include_settings_sync_surface() { + let commands = QuadrantHost::supported_commands(); + assert!(commands.contains(&"get_quadrant_settings")); + assert!(commands.contains(&"submit_quadrant_settings")); + assert!(!commands.contains(&"open_modpacks_folder")); + assert!(!commands.contains(&"request_check_for_updates")); + } + + #[test] + fn host_options_default_to_compatible_keyring_name() { + let options = + QuadrantHostOptions::new(PathBuf::from("C:/quadrant"), "client", "secret", "api-key"); + assert_eq!(options.keyring_service_name, KEYRING_SERVICE); + } + + #[tokio::test] + async fn event_subscription_receives_emitted_envelopes() { + let temp_dir = tempdir().unwrap(); + let host = QuadrantHost::new(QuadrantHostOptions::new( + temp_dir.path().to_path_buf(), + "client", + "secret", + "api-key", + )) + .unwrap(); + + let mut receiver = host.subscribe_events(); + host.inner + .event_sink + .emit("refreshSyncedModpacks", "modpack-1") + .unwrap(); + + let envelope: HostEventEnvelope = receiver.recv().await.unwrap(); + assert_eq!(envelope.event, "refreshSyncedModpacks"); + assert_eq!(envelope.payload, Value::String("modpack-1".to_string())); + } +} diff --git a/src-tauri/crates/quadrant-napi/Cargo.toml b/src-tauri/crates/quadrant-napi/Cargo.toml new file mode 100644 index 00000000..1cbb40ee --- /dev/null +++ b/src-tauri/crates/quadrant-napi/Cargo.toml @@ -0,0 +1,29 @@ +[package] +name = "quadrant-napi" +version.workspace = true +edition = "2024" +build = "build.rs" + +[features] +default = ["proprietary"] +curseforge = ["quadrant-host/curseforge"] +telemetry = [] +quadrant_id = [] +proprietary = ["curseforge", "telemetry", "quadrant_id"] + +[lib] +crate-type = ["cdylib"] + +[dependencies] +napi = { version = "3", default-features = false, features = [ + "napi8", + "tokio_rt", + "serde-json", +] } +napi-derive = "3" +quadrant-host = { path = "../quadrant-host" } +serde_json = "1.0.149" +tokio = { version = "1.51.0", features = ["full"] } + +[build-dependencies] +napi-build = "2" diff --git a/src-tauri/crates/quadrant-napi/build.rs b/src-tauri/crates/quadrant-napi/build.rs new file mode 100644 index 00000000..0f1b0100 --- /dev/null +++ b/src-tauri/crates/quadrant-napi/build.rs @@ -0,0 +1,3 @@ +fn main() { + napi_build::setup(); +} diff --git a/src-tauri/crates/quadrant-napi/index.js b/src-tauri/crates/quadrant-napi/index.js new file mode 100644 index 00000000..275f75bd --- /dev/null +++ b/src-tauri/crates/quadrant-napi/index.js @@ -0,0 +1,38 @@ +/** + * eslint-disable @typescript-eslint/no-require-imports + * + * @format + */ + +/** @format */ + +import { createRequire } from "node:module"; + +const require = createRequire(import.meta.url); + +const candidates = [ + process.env.QUADRANT_NAPI_BINDING, + "./index.node", + "./quadrant-napi.node", + "./quadrant_napi.node", +].filter(Boolean); + +let loaded; +let lastError; + +for (const candidate of candidates) { + try { + loaded = require(candidate); + break; + } catch (error) { + lastError = error; + } +} + +if (!loaded) { + throw new Error( + `Failed to load Quadrant N-API binding. Set QUADRANT_NAPI_BINDING to the built addon path. Last error: ${lastError}`, + ); +} + +export default loaded; diff --git a/src-tauri/crates/quadrant-napi/package.json b/src-tauri/crates/quadrant-napi/package.json new file mode 100644 index 00000000..227faccf --- /dev/null +++ b/src-tauri/crates/quadrant-napi/package.json @@ -0,0 +1,14 @@ +{ + "name": "@quadrant/quadrant-napi", + "version": "26.4.0", + "type": "module", + "main": "index.js", + "exports": { + ".": "./index.js" + }, + "files": [ + "index.js", + "index.node", + "*.node" + ] +} diff --git a/src-tauri/crates/quadrant-napi/src/lib.rs b/src-tauri/crates/quadrant-napi/src/lib.rs new file mode 100644 index 00000000..57a19f82 --- /dev/null +++ b/src-tauri/crates/quadrant-napi/src/lib.rs @@ -0,0 +1,211 @@ +use std::path::PathBuf; +use std::sync::{Arc, Mutex}; + +use napi::{Result, bindgen_prelude::Function, threadsafe_function::ThreadsafeFunctionCallMode}; +use napi_derive::napi; +use quadrant_host::{QuadrantHost, QuadrantHostOptions}; +use serde_json::Value; + +#[napi(object)] +pub struct QuadrantHostInit { + pub data_dir: String, + pub mc_folder: Option, + pub api_base_url: Option, + pub oauth_client_id: String, + pub oauth_client_secret: String, + pub quadrant_api_key: String, + pub config_store_name: Option, + pub update_store_name: Option, + pub keyring_service_name: Option, + pub app_version: Option, + pub os_name: Option, + pub user_agent: Option, +} + +#[napi] +pub struct QuadrantHostAddon { + host: QuadrantHost, + event_forwarder: Arc>>>, +} + +#[napi] +impl QuadrantHostAddon { + #[napi(constructor)] + pub fn new(options: QuadrantHostInit) -> Result { + let mut host_options = QuadrantHostOptions::new( + PathBuf::from(options.data_dir), + options.oauth_client_id, + options.oauth_client_secret, + options.quadrant_api_key, + ); + host_options.mc_folder = options.mc_folder.map(PathBuf::from); + host_options.api_base_url = options.api_base_url; + if let Some(value) = options.config_store_name { + host_options.config_store_name = value; + } + if let Some(value) = options.update_store_name { + host_options.update_store_name = value; + } + if let Some(value) = options.keyring_service_name { + host_options.keyring_service_name = value; + } + if let Some(value) = options.app_version { + host_options.app_version = value; + } + if let Some(value) = options.os_name { + host_options.os_name = value; + } + if let Some(value) = options.user_agent { + host_options.user_agent = value; + } + + Ok(Self { + host: QuadrantHost::new(host_options) + .map_err(|error| napi::Error::from_reason(error.to_string()))?, + event_forwarder: Arc::new(Mutex::new(None)), + }) + } + + #[napi] + pub async fn start_background_workers(&self) -> Result<()> { + self.host + .start_background_workers() + .await + .map_err(to_napi_error) + } + + #[napi] + pub async fn stop_background_workers(&self) -> Result<()> { + self.host + .stop_background_workers() + .await + .map_err(to_napi_error) + } + + #[napi] + pub async fn shutdown(&self) -> Result<()> { + self.host.shutdown().await.map_err(to_napi_error)?; + self.clear_event_forwarder(); + Ok(()) + } + + #[napi] + pub fn on_event(&self, callback: Function<'_, String, ()>) -> Result<()> { + self.clear_event_forwarder(); + + let tsfn = callback.build_threadsafe_function().build()?; + let mut receiver = self.host.subscribe_events(); + let handle = napi::bindgen_prelude::within_runtime_if_available(|| { + tokio::spawn(async move { + loop { + match receiver.recv().await { + Ok(event) => { + if let Ok(payload) = serde_json::to_string(&event) { + let _ = tsfn.call(payload, ThreadsafeFunctionCallMode::NonBlocking); + } + } + Err(tokio::sync::broadcast::error::RecvError::Lagged(_)) => {} + Err(tokio::sync::broadcast::error::RecvError::Closed) => break, + } + } + }) + }); + + *self + .event_forwarder + .lock() + .map_err(|_| napi::Error::from_reason("event forwarder is busy"))? = Some(handle); + Ok(()) + } + + #[napi] + pub async fn invoke(&self, command: String, payload: Option) -> Result { + self.host + .invoke(&command, payload.unwrap_or(Value::Null)) + .await + .map_err(to_napi_error) + } + + #[napi] + pub async fn init_config(&self) -> Result<()> { + self.host.init_config().map_err(to_napi_error) + } + + #[napi] + pub async fn get_minecraft_folder(&self) -> Result { + Ok(self + .host + .get_minecraft_folder() + .map_err(to_napi_error)? + .to_string_lossy() + .to_string()) + } + + #[napi] + pub async fn get_modpacks(&self, hide_free: Option) -> Result { + self.host + .get_modpacks(hide_free.unwrap_or(false)) + .await + .and_then(|value| serde_json::to_value(value).map_err(Into::into)) + .map_err(to_napi_error) + } + + #[napi] + pub async fn get_account_info(&self) -> Result { + self.host + .get_account_info() + .await + .and_then(|value| serde_json::to_value(value).map_err(Into::into)) + .map_err(to_napi_error) + } + + #[napi] + pub async fn get_news(&self) -> Result { + self.host + .get_news() + .await + .and_then(|value| serde_json::to_value(value).map_err(Into::into)) + .map_err(to_napi_error) + } + + #[napi] + pub async fn install_mod(&self, args: Value) -> Result<()> { + self.host + .invoke("install_mod", args) + .await + .map_err(to_napi_error) + .map(|_| ()) + } + + #[napi] + pub async fn sync_modpack(&self, modpack: Value, overwrite: bool) -> Result<()> { + self.host + .invoke( + "sync_modpack", + serde_json::json!({ + "modpack": modpack, + "overwrite": overwrite, + }), + ) + .await + .map_err(to_napi_error) + .map(|_| ()) + } +} + +impl QuadrantHostAddon { + fn clear_event_forwarder(&self) { + if let Some(handle) = self + .event_forwarder + .lock() + .expect("event forwarder lock poisoned") + .take() + { + handle.abort(); + } + } +} + +fn to_napi_error(error: impl ToString) -> napi::Error { + napi::Error::from_reason(error.to_string()) +} diff --git a/src-tauri/src/account/id.rs b/src-tauri/src/account/id.rs index 3529339a..10017ce1 100644 --- a/src-tauri/src/account/id.rs +++ b/src-tauri/src/account/id.rs @@ -1,208 +1,16 @@ -use std::{collections::HashMap, time::Duration}; - -use anyhow::anyhow; -use futures::StreamExt; -use serde::Deserialize; -use tauri::{AppHandle, Emitter, Manager}; -use tauri_plugin_notification::NotificationExt; -use tauri_plugin_store::StoreExt; -use tokio::sync::Mutex; -use tokio_tungstenite::{ - connect_async, - tungstenite::{Message, client::IntoClientRequest}, -}; -use url::Url; - -use crate::{ - AppState, - account::{ - self, - quadrant_settings_sync::{get_quadrant_settings, submit_quadrant_settings}, - quadrant_sync::{SyncedModpack, get_synced_modpacks, persist_sync_metadata}, - }, - mc_mod::get_user_agent, - modpacks::general::{InstalledModpack, LocalModpack, install_modpack}, - tauri_adapter::{TauriSecretStore, mc_folder}, -}; +use quadrant_host::QuadrantHost; +use tauri::{AppHandle, Manager}; pub use quadrant_core::account::id::{ AccountInfo, Notification, NotificationCursor, NotificationWsFrame, OAuth2Response, }; -const NOTIFICATION_CURSOR_CREATED_AT_KEY: &str = "notificationCursorCreatedAt"; -const NOTIFICATION_CURSOR_NOTIFICATION_ID_KEY: &str = "notificationCursorNotificationId"; -const SHOWN_NOTIFICATIONS_KEY: &str = "shownNotifications"; -const SETTINGS_SYNC_INTERVAL_SECS: u64 = 120; -const WS_REPLAY_LIMIT: usize = 500; -const NOTIFICATION_TITLE: &str = "Quadrant ID"; -const REFRESH_SYNCED_MODPACKS_EVENT: &str = "refreshSyncedModpacks"; - -#[derive(Default, Clone)] -pub struct NotificationRuntimeState { - pub by_key: HashMap, - pub key_by_notification_id: HashMap, - pub ordered_keys: Vec, - pub processed_modpack_sync_by_key: HashMap, - pub cursor: NotificationCursor, - pub reconnect_attempt: u32, -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -struct UpsertOutcome { - changed: bool, - inserted: bool, -} - -struct MergeNotificationsOutcome { - notifications_for_ui: Option>, - modpack_sync_notifications: Vec, -} - -impl NotificationRuntimeState { - fn upsert(&mut self, notification: Notification) -> UpsertOutcome { - self.advance_cursor(¬ification); - let identity_key = notification_identity_key(¬ification); - match self.by_key.get(&identity_key) { - Some(existing) if existing == ¬ification => UpsertOutcome { - changed: false, - inserted: false, - }, - Some(_) => { - if let Some(previous) = self - .by_key - .insert(identity_key.clone(), notification.clone()) - { - self.key_by_notification_id - .remove(previous.notification_id.as_str()); - } - self.key_by_notification_id - .insert(notification.notification_id.clone(), identity_key); - self.sort_keys(); - UpsertOutcome { - changed: true, - inserted: false, - } - } - None => { - self.ordered_keys.push(identity_key.clone()); - self.by_key - .insert(identity_key.clone(), notification.clone()); - self.key_by_notification_id - .insert(notification.notification_id.clone(), identity_key); - self.sort_keys(); - UpsertOutcome { - changed: true, - inserted: true, - } - } - } - } - - fn mark_read(&mut self, notification_id: &str) -> bool { - let Some(identity_key) = self.key_by_notification_id.get(notification_id).cloned() else { - return false; - }; - let Some(notification) = self.by_key.get_mut(identity_key.as_str()) else { - return false; - }; - if notification.read { - return false; - } - notification.read = true; - true - } - - fn notifications_for_ui(&self) -> Vec { - let mut notifications = self - .ordered_keys - .iter() - .filter_map(|key| self.by_key.get(key)) - .cloned() - .collect::>(); - notifications.sort_by(|a, b| { - b.created_at_unix - .cmp(&a.created_at_unix) - .then_with(|| b.notification_id.cmp(&a.notification_id)) - }); - notifications - } - - fn advance_cursor(&mut self, notification: &Notification) { - let current_created_at = self.cursor.created_at.as_deref(); - let current_notification_id = self.cursor.notification_id.as_deref(); - let is_newer = match current_created_at { - None => true, - Some(created_at) => { - notification.created_at.as_str() > created_at - || (notification.created_at.as_str() == created_at - && current_notification_id - .map(|id| notification.notification_id.as_str() > id) - .unwrap_or(true)) - } - }; - - if is_newer { - self.cursor = NotificationCursor { - created_at: Some(notification.created_at.clone()), - notification_id: Some(notification.notification_id.clone()), - }; - } - } - - fn sort_keys(&mut self) { - self.ordered_keys.sort_by(|left, right| { - let left_notification = self.by_key.get(left).expect("missing notification"); - let right_notification = self.by_key.get(right).expect("missing notification"); - left_notification - .created_at_unix - .cmp(&right_notification.created_at_unix) - .then_with(|| { - left_notification - .notification_id - .cmp(&right_notification.notification_id) - }) - }); - } - - fn should_process_modpack_sync_notification(&self, notification: &Notification) -> bool { - if !is_modpack_sync_notification(notification) { - return false; - } - - let identity_key = notification_identity_key(notification); - self.processed_modpack_sync_by_key - .get(identity_key.as_str()) - .map(|notification_id| notification_id != ¬ification.notification_id) - .unwrap_or(true) - } - - fn mark_modpack_sync_processed(&mut self, notification: &Notification) { - if !is_modpack_sync_notification(notification) { - return; - } - - let identity_key = notification_identity_key(notification); - self.processed_modpack_sync_by_key - .insert(identity_key, notification.notification_id.clone()); - } -} - -#[derive(Debug, Deserialize)] -struct ModpackSyncPayload { - notification_type: String, - modpack: SyncedModpack, -} - #[tauri::command] -pub async fn get_account_info() -> Result { - quadrant_core::account::id::get_account_info_with_refresh( - &TauriSecretStore, - &get_user_agent(), - env!("QUADRANT_OAUTH2_CLIENT_ID"), - env!("QUADRANT_OAUTH2_CLIENT_SECRET"), - ) - .await - .map_err(tauri::Error::from) +pub async fn get_account_info(app: AppHandle) -> Result { + app.state::() + .get_account_info() + .await + .map_err(tauri::Error::from) } #[tauri::command] @@ -211,23 +19,15 @@ pub async fn oauth2_login( redirect_uri: String, app: AppHandle, ) -> Result<(), tauri::Error> { - quadrant_core::account::id::oauth2_login( - &TauriSecretStore, - &get_user_agent(), - env!("QUADRANT_OAUTH2_CLIENT_ID"), - env!("QUADRANT_OAUTH2_CLIENT_SECRET"), - code, - redirect_uri, - ) - .await - .map_err(tauri::Error::from)?; - app.emit("recheckAccountToken", "")?; - Ok(()) + app.state::() + .oauth2_login(code, redirect_uri) + .await + .map_err(tauri::Error::from) } #[tauri::command] -pub fn oauth2_client_id() -> String { - env!("QUADRANT_OAUTH2_CLIENT_ID").to_string() +pub fn oauth2_client_id(app: AppHandle) -> String { + app.state::().oauth2_client_id() } #[tauri::command] @@ -235,899 +35,8 @@ pub async fn read_notification( notification_id: String, app: AppHandle, ) -> Result<(), tauri::Error> { - quadrant_core::account::id::read_notification( - &TauriSecretStore, - &get_user_agent(), - notification_id.clone(), - ) - .await - .map_err(tauri::Error::from)?; - - if let Some(notifications) = mark_notification_read_and_snapshot(&app, ¬ification_id).await { - emit_notification_refresh(&app, notifications)?; - } - - Ok(()) -} - -pub fn start_notification_worker(app: AppHandle) { - tokio::task::spawn(async move { - loop { - if let Err(error) = bootstrap_notifications(&app).await { - log::error!("Notification bootstrap failed: {error}"); - } - - let stream_result = run_notification_socket(app.clone()).await; - if let Err(error) = stream_result { - log::warn!("Notification socket cycle ended: {error}"); - } - - if let Err(error) = bootstrap_notifications(&app).await { - log::warn!("Notification catch-up failed after socket cycle: {error}"); - } - - let delay = { - let state = app.state::>(); - let mut state = state.lock().await; - state.notification_state.reconnect_attempt = - state.notification_state.reconnect_attempt.saturating_add(1); - reconnect_delay(state.notification_state.reconnect_attempt) - }; - tokio::time::sleep(delay).await; - } - }); -} - -pub fn start_settings_sync_worker(app: AppHandle) { - tokio::task::spawn(async move { - let mut interval = tokio::time::interval(Duration::from_secs(SETTINGS_SYNC_INTERVAL_SECS)); - loop { - interval.tick().await; - if let Err(error) = sync_remote_settings(app.clone()).await { - log::warn!("Settings sync worker failed: {error}"); - } - } - }); -} - -async fn bootstrap_notifications(app: &AppHandle) -> Result<(), anyhow::Error> { - let cursor = load_notification_cursor(app)?; - let (notifications, next_cursor) = - quadrant_core::account::id::get_notification_history_all_since( - &TauriSecretStore, - &get_user_agent(), - Some(&cursor), - None, - true, - ) - .await?; - - if notifications.is_empty() && next_cursor == cursor { - return Ok(()); - } - - let merge_outcome = { - let state = app.state::>(); - let mut state = state.lock().await; - let runtime = &mut state.notification_state; - let merge_outcome = merge_notifications_and_collect_updates(runtime, notifications); - if runtime.cursor != next_cursor { - runtime.cursor = next_cursor.clone(); - } - merge_outcome - }; - - persist_notification_cursor(app, &next_cursor)?; - - if let Some(notifications) = merge_outcome.notifications_for_ui { - emit_notification_refresh(app, notifications)?; - } - - for notification in merge_outcome.modpack_sync_notifications { - match handle_modpack_sync_notification(app, ¬ification).await { - Ok(()) => { - mark_modpack_sync_processed(app, ¬ification).await; - } - Err(error) => { - log::warn!("Failed to process bootstrapped modpack sync notification: {error}"); - } - } - } - - set_reconnect_attempt(app, 0).await; - - Ok(()) -} - -fn merge_notifications_and_collect_updates( - runtime: &mut NotificationRuntimeState, - mut notifications: Vec, -) -> MergeNotificationsOutcome { - notifications.sort_by(|left, right| { - left.created_at_unix - .cmp(&right.created_at_unix) - .then_with(|| left.notification_id.cmp(&right.notification_id)) - }); - - let mut changed = false; - let mut latest_modpack_sync_by_key = HashMap::new(); - let mut modpack_sync_order = Vec::new(); - - for notification in notifications { - let should_queue_modpack_sync = runtime.should_process_modpack_sync_notification(¬ification); - let outcome = runtime.upsert(notification.clone()); - changed |= outcome.changed; - - if should_queue_modpack_sync { - let identity_key = notification_identity_key(¬ification); - latest_modpack_sync_by_key.insert(identity_key.clone(), notification); - if let Some(index) = modpack_sync_order - .iter() - .position(|existing| existing == &identity_key) - { - modpack_sync_order.remove(index); - } - modpack_sync_order.push(identity_key); - } - } - - MergeNotificationsOutcome { - notifications_for_ui: changed.then(|| runtime.notifications_for_ui()), - modpack_sync_notifications: modpack_sync_order - .into_iter() - .filter_map(|identity_key| latest_modpack_sync_by_key.remove(&identity_key)) - .collect(), - } -} - -async fn run_notification_socket(app: AppHandle) -> Result<(), anyhow::Error> { - let token = account::get_account_token()?; - let (cursor, connection_id) = { - let state = app.state::>(); - let state = state.lock().await; - ( - state.notification_state.cursor.clone(), - state.notification_connection_id.clone(), - ) - }; - - let ws_url = build_notification_ws_url(&cursor, &connection_id)?; - let mut request = ws_url.as_str().into_client_request()?; - request - .headers_mut() - .insert("Authorization", format!("Bearer {token}").parse()?); - request - .headers_mut() - .insert("User-Agent", get_user_agent().parse()?); - - let (mut socket, _) = connect_async(request).await?; - set_reconnect_attempt(&app, 0).await; - - while let Some(frame) = socket.next().await { - match frame? { - Message::Text(payload) => { - let payload = payload.to_string(); - handle_notification_ws_frame(&app, &payload).await?; - } - Message::Close(frame) => { - let reason = frame - .map(|f| f.reason.to_string()) - .unwrap_or_else(|| "closed".to_string()); - return Err(anyhow!("notification websocket closed: {reason}")); - } - Message::Ping(_) | Message::Pong(_) | Message::Binary(_) | Message::Frame(_) => {} - } - } - - Err(anyhow!("notification websocket stream ended")) -} - -async fn handle_notification_ws_frame(app: &AppHandle, payload: &str) -> Result<(), anyhow::Error> { - let frame = serde_json::from_str::(payload)?; - match frame { - NotificationWsFrame::Connected { server_time } => { - log::info!("Notification websocket connected at {server_time}"); - } - NotificationWsFrame::Notification { - delivery, - notification, - } => { - let should_notify = delivery == "live" && !notification.read; - let notification_id = notification.notification_id.clone(); - let simple_message = notification_simple_message(¬ification); - let is_modpack_sync = is_modpack_sync_notification(¬ification); - let notification_for_processing = notification.clone(); - - let notifications_to_emit = { - let state = app.state::>(); - let mut state = state.lock().await; - let outcome = state.notification_state.upsert(notification); - outcome.changed.then(|| { - ( - state.notification_state.notifications_for_ui(), - outcome.inserted, - ) - }) - }; - - if let Some((notifications, inserted)) = notifications_to_emit { - emit_notification_refresh(app, notifications)?; - if should_notify && inserted { - maybe_show_native_notification( - app, - ¬ification_for_processing, - ¬ification_id, - simple_message.as_deref(), - ) - .await?; - } - } - - if is_modpack_sync { - match handle_modpack_sync_notification(app, ¬ification_for_processing).await { - Ok(()) => { - mark_modpack_sync_processed(app, ¬ification_for_processing).await; - } - Err(error) => { - log::warn!("Failed to process modpack sync notification: {error}"); - } - } - } - } - NotificationWsFrame::ReplayComplete { truncated, .. } => { - if truncated { - bootstrap_notifications(app).await?; - } - } - NotificationWsFrame::Error { error } => { - if error.to_lowercase().contains("lagged") { - bootstrap_notifications(app).await?; - } - return Err(anyhow!("notification websocket error frame: {error}")); - } - } - - Ok(()) -} - -fn build_notification_ws_url( - cursor: &NotificationCursor, - connection_id: &str, -) -> Result { - let base = std::env::var("QUADRANT_API_BASE_URL") - .unwrap_or_else(|_| quadrant_core::account::QNT_BASE_URL.to_string()); - let mut url = Url::parse(&base)?; - match url.scheme() { - "https" => url - .set_scheme("wss") - .map_err(|_| anyhow!("invalid ws scheme"))?, - "http" => url - .set_scheme("ws") - .map_err(|_| anyhow!("invalid ws scheme"))?, - "wss" | "ws" => {} - _ => return Err(anyhow!("unsupported backend scheme")), - } - url.set_path("/api/v3/account/notifications/ws"); - { - let mut query = url.query_pairs_mut(); - if let Some(created_at) = cursor.created_at.as_deref() { - query.append_pair("since", created_at); - } - query.append_pair("replay_limit", &WS_REPLAY_LIMIT.to_string()); - query.append_pair("modpack_sync", "true"); - query.append_pair("connection_id", connection_id); - } - Ok(url) -} - -fn reconnect_delay(attempt: u32) -> Duration { - let secs = match attempt { - 0 => 1, - 1 => 1, - 2 => 2, - 3 => 5, - 4 => 10, - 5 => 30, - _ => 60, - }; - Duration::from_secs(secs) -} - -fn emit_notification_refresh( - app: &AppHandle, - notifications: Vec, -) -> Result<(), anyhow::Error> { - app.emit("refreshNotifications", notifications)?; - Ok(()) -} - -fn emit_synced_modpacks_refresh(app: &AppHandle, modpack_id: &str) -> Result<(), anyhow::Error> { - app.emit(REFRESH_SYNCED_MODPACKS_EVENT, modpack_id)?; - Ok(()) -} - -async fn mark_notification_read_and_snapshot( - app: &AppHandle, - notification_id: &str, -) -> Option> { - let state = app.state::>(); - let mut state = state.lock().await; - state - .notification_state - .mark_read(notification_id) - .then(|| state.notification_state.notifications_for_ui()) -} - -async fn set_reconnect_attempt(app: &AppHandle, attempt: u32) { - let state = app.state::>(); - let mut state = state.lock().await; - state.notification_state.reconnect_attempt = attempt; -} - -async fn mark_modpack_sync_processed(app: &AppHandle, notification: &Notification) { - let state = app.state::>(); - let mut state = state.lock().await; - state - .notification_state - .mark_modpack_sync_processed(notification); -} - -fn load_notification_cursor(app: &AppHandle) -> Result { - let store = app.store("config.json")?; - Ok(NotificationCursor { - created_at: store - .get(NOTIFICATION_CURSOR_CREATED_AT_KEY) - .and_then(|value| value.as_str().map(ToOwned::to_owned)), - notification_id: store - .get(NOTIFICATION_CURSOR_NOTIFICATION_ID_KEY) - .and_then(|value| value.as_str().map(ToOwned::to_owned)), - }) -} - -fn persist_notification_cursor( - app: &AppHandle, - cursor: &NotificationCursor, -) -> Result<(), anyhow::Error> { - let store = app.store("config.json")?; - match &cursor.created_at { - Some(created_at) => { - store.set( - NOTIFICATION_CURSOR_CREATED_AT_KEY, - serde_json::Value::String(created_at.clone()), - ); - } - None => { - store.delete(NOTIFICATION_CURSOR_CREATED_AT_KEY); - } - }; - match &cursor.notification_id { - Some(notification_id) => { - store.set( - NOTIFICATION_CURSOR_NOTIFICATION_ID_KEY, - serde_json::Value::String(notification_id.clone()), - ); - } - None => { - store.delete(NOTIFICATION_CURSOR_NOTIFICATION_ID_KEY); - } - }; - store.save()?; - Ok(()) -} - -async fn maybe_show_native_notification( - app: &AppHandle, - notification: &Notification, - notification_id: &str, - body: Option<&str>, -) -> Result<(), anyhow::Error> { - let body = match body { - Some(body) if !body.is_empty() => body, - _ => return Ok(()), - }; - - if is_modpack_sync_notification(notification) - && !should_show_modpack_sync_notification(app, notification).await - { - return Ok(()); - } - - let store = app.store("config.json")?; - let mut shown_notifications = store - .get(SHOWN_NOTIFICATIONS_KEY) - .and_then(|value| serde_json::from_value::>(value).ok()) - .unwrap_or_default(); - if shown_notifications.iter().any(|id| id == notification_id) { - return Ok(()); - } - - app.notification() - .builder() - .title(NOTIFICATION_TITLE) - .body(body) - .show()?; - - shown_notifications.push(notification_id.to_string()); - store.set( - SHOWN_NOTIFICATIONS_KEY, - serde_json::to_value(shown_notifications)?, - ); - store.save()?; - - Ok(()) -} - -fn notification_simple_message(notification: &Notification) -> Option { - serde_json::from_str::(¬ification.message) - .ok() - .and_then(|message| { - message - .get("simple_message") - .and_then(|value| value.as_str().map(ToOwned::to_owned)) - }) -} - -fn notification_updated_by(notification: &Notification) -> Option { - serde_json::from_str::(¬ification.message) - .ok() - .and_then(|message| { - message - .get("updated_by") - .and_then(|value| value.as_str().map(ToOwned::to_owned)) - }) -} - -fn parse_notification_message(notification: &Notification) -> Option { - serde_json::from_str::(¬ification.message) - .ok() - .filter(|payload| payload.notification_type == "modpack_sync") -} - -fn is_modpack_sync_notification(notification: &Notification) -> bool { - notification.notification_type.as_deref() == Some("modpack_sync") -} - -fn notification_identity_key(notification: &Notification) -> String { - if is_modpack_sync_notification(notification) - && let Some(resource_id) = notification.resource_id.as_deref() - { - return format!("modpack_sync:{resource_id}"); - } - - notification.notification_id.clone() -} - -async fn should_show_modpack_sync_notification( - app: &AppHandle, - notification: &Notification, -) -> bool { - let Ok(config) = app.store("config.json") else { - return true; - }; - if !config - .get("showModpackUpdateNotifications") - .and_then(|value| value.as_bool()) - .unwrap_or(true) - { - return false; - } - - let Some(updated_by) = notification_updated_by(notification) else { - return true; - }; - - let updated_by = normalize_identity(updated_by.as_str()); - if updated_by.is_empty() { - return true; - } - - match quadrant_core::account::id::get_account_info_with_refresh( - &TauriSecretStore, - &get_user_agent(), - env!("QUADRANT_OAUTH2_CLIENT_ID"), - env!("QUADRANT_OAUTH2_CLIENT_SECRET"), - ) - .await - { - Ok(account_info) => { - let identities = [account_info.name, account_info.login]; - !identities - .iter() - .map(|identity| normalize_identity(identity.as_str())) - .any(|identity| !identity.is_empty() && identity == updated_by) - } - Err(error) => { - log::warn!("Failed to resolve current account for notification filtering: {error}"); - true - } - } -} - -fn normalize_identity(identity: &str) -> String { - identity.trim().to_lowercase() -} - -async fn handle_modpack_sync_notification( - app: &AppHandle, - notification: &Notification, -) -> Result<(), anyhow::Error> { - let synced_modpack = resolve_synced_modpack_from_notification(notification).await?; - - let config = app.store("config.json")?; - let auto_quadrant_sync = config - .get("autoQuadrantSync") - .and_then(|value| value.as_bool()) - .unwrap_or(false); - - if auto_quadrant_sync { - let local_modpack = resolve_local_modpack_for_sync(app, &synced_modpack).await?; - let local_modpack = - maybe_backfill_local_modpack_id(app, local_modpack, &synced_modpack).await?; - - if let Some(local_modpack) = local_modpack { - maybe_apply_remote_modpack_update(app, &local_modpack, &synced_modpack).await?; - } - } - - emit_synced_modpacks_refresh(app, &synced_modpack.modpack_id)?; - - Ok(()) -} - -async fn resolve_synced_modpack_from_notification( - notification: &Notification, -) -> Result { - if let Some(payload) = parse_notification_message(notification) { - return Ok(payload.modpack); - } - - let resource_id = notification - .resource_id - .as_deref() - .ok_or_else(|| anyhow!("modpack sync notification missing resource_id"))?; - let mut synced_modpacks = get_synced_modpacks(false, Some(resource_id.to_string())).await?; - synced_modpacks - .drain(..) - .next() - .ok_or_else(|| anyhow!("modpack sync fallback fetch returned no modpack")) -} - -async fn resolve_local_modpack_for_sync( - app: &AppHandle, - synced_modpack: &SyncedModpack, -) -> Result, anyhow::Error> { - let modpacks = quadrant_core::modpacks::get_modpacks(&mc_folder(app)?, true)?; - - if let Some(local_modpack) = modpacks - .iter() - .find(|modpack| modpack.modpack_id.as_deref() == Some(synced_modpack.modpack_id.as_str())) - .cloned() - { - return Ok(Some(local_modpack)); - } - - Ok(modpacks - .into_iter() - .find(|modpack| modpack.last_synced != 0 && modpack.name == synced_modpack.name)) -} - -async fn maybe_backfill_local_modpack_id( - app: &AppHandle, - local_modpack: Option, - synced_modpack: &SyncedModpack, -) -> Result, anyhow::Error> { - let Some(mut local_modpack) = local_modpack else { - return Ok(None); - }; - - if local_modpack.modpack_id.as_deref() == Some(synced_modpack.modpack_id.as_str()) { - return Ok(Some(local_modpack)); - } - - let local_sync_time = u64::try_from(local_modpack.last_synced / 1000).unwrap_or_default(); - persist_sync_metadata( - app, - &local_modpack.name, - local_sync_time, - Some(synced_modpack.modpack_id.as_str()), - )?; - local_modpack.modpack_id = Some(synced_modpack.modpack_id.clone()); - - Ok(Some(local_modpack)) -} - -async fn maybe_apply_remote_modpack_update( - app: &AppHandle, - local_modpack: &LocalModpack, - synced_modpack: &SyncedModpack, -) -> Result<(), anyhow::Error> { - let local_sync_time = local_modpack.last_synced / 1000; - if synced_modpack.last_synced <= local_sync_time { - return Ok(()); - } - - if !begin_modpack_update(app, &local_modpack.name).await { - return Ok(()); - } - - let install_result = install_modpack( - InstalledModpack { - mod_loader: synced_modpack.mod_loader, - name: synced_modpack.name.clone(), - version: synced_modpack.minecraft_version.clone(), - mods: serde_json::from_str(&synced_modpack.mods)?, - }, - app.clone(), - ) - .await; - - if let Err(error) = install_result { - finish_modpack_update(app, &local_modpack.name).await; - return Err(error.into()); - } - - persist_sync_metadata( - app, - &local_modpack.name, - synced_modpack.last_synced as u64, - Some(synced_modpack.modpack_id.as_str()), - )?; - finish_modpack_update(app, &local_modpack.name).await; - - Ok(()) -} - -async fn begin_modpack_update(app: &AppHandle, modpack_name: &str) -> bool { - let state = app.state::>(); - let mut state = state.lock().await; - if state - .updated_modpacks - .iter() - .any(|name| name == modpack_name) - { - return false; - } - - state.updated_modpacks.push(modpack_name.to_string()); - true -} - -async fn finish_modpack_update(app: &AppHandle, modpack_name: &str) { - let state = app.state::>(); - let mut state = state.lock().await; - state.updated_modpacks.retain(|name| name != modpack_name); -} - -async fn sync_remote_settings(app: AppHandle) -> Result<(), anyhow::Error> { - let config = app.store("config.json")?; - let auto_settings_sync = config - .get("syncSettings") - .and_then(|value| value.as_bool()) - .unwrap_or(false); - if !auto_settings_sync { - return Ok(()); - } - - let res = get_quadrant_settings(app.clone()).await; - if let Err(error) = res - && error.to_string() == "Current settings are newer" - { - submit_quadrant_settings(app).await?; - } - - Ok(()) -} - -#[cfg(test)] -mod tests { - use super::{ - Notification, NotificationCursor, NotificationRuntimeState, build_notification_ws_url, - merge_notifications_and_collect_updates, - }; - - fn notification(id: &str, unix: i64, read: bool) -> Notification { - Notification { - notification_id: id.to_string(), - user_id: "u1".to_string(), - notification_type: Some("invite_to_sync".to_string()), - resource_id: None, - message: "{\"simple_message\":\"hello\"}".to_string(), - created_at: format!("2026-03-20T10:{unix:02}:00Z"), - created_at_unix: unix, - read, - } - } - - #[test] - fn merge_notifications_deduplicates_and_orders() { - let mut state = NotificationRuntimeState::default(); - assert!(merge_notifications_and_collect_updates( - &mut state, - vec![notification("n1", 1, false), notification("n2", 2, false)] - ) - .notifications_for_ui - .is_some()); - assert!( - merge_notifications_and_collect_updates(&mut state, vec![notification("n1", 1, false)]) - .notifications_for_ui - .is_none() - ); - - let notifications = state.notifications_for_ui(); - assert_eq!(notifications[0].notification_id, "n2"); - assert_eq!(notifications[1].notification_id, "n1"); - assert_eq!( - state.cursor, - NotificationCursor { - created_at: Some("2026-03-20T10:02:00Z".to_string()), - notification_id: Some("n2".to_string()), - } - ); - } - - #[test] - fn mark_notification_read_local_updates_cached_notification() { - let mut state = NotificationRuntimeState::default(); - let _ = merge_notifications_and_collect_updates(&mut state, vec![notification("n1", 1, false)]); - assert!(state.mark_read("n1")); - assert!(state.by_key.get("n1").unwrap().read); - assert!(!state.mark_read("n1")); - } - - #[test] - fn modpack_sync_notifications_replace_previous_resource_entry() { - let mut state = NotificationRuntimeState::default(); - let first = Notification { - notification_id: "n1".to_string(), - user_id: "u1".to_string(), - notification_type: Some("modpack_sync".to_string()), - resource_id: Some("modpack-1".to_string()), - message: "{\"notification_type\":\"modpack_sync\",\"simple_message\":\"hello\"}" - .to_string(), - created_at: "2026-03-20T10:01:00Z".to_string(), - created_at_unix: 1, - read: false, - }; - let second = Notification { - notification_id: "n2".to_string(), - user_id: "u1".to_string(), - notification_type: Some("modpack_sync".to_string()), - resource_id: Some("modpack-1".to_string()), - message: "{\"notification_type\":\"modpack_sync\",\"simple_message\":\"updated\"}" - .to_string(), - created_at: "2026-03-20T10:02:00Z".to_string(), - created_at_unix: 2, - read: false, - }; - - assert!( - merge_notifications_and_collect_updates(&mut state, vec![first]) - .notifications_for_ui - .is_some() - ); - assert!( - merge_notifications_and_collect_updates(&mut state, vec![second]) - .notifications_for_ui - .is_some() - ); - - let notifications = state.notifications_for_ui(); - assert_eq!(notifications.len(), 1); - assert_eq!(notifications[0].notification_id, "n2"); - assert_eq!( - state.key_by_notification_id.get("n2").map(String::as_str), - Some("modpack_sync:modpack-1") - ); - } - - #[test] - fn notification_websocket_url_includes_modpack_sync_and_connection_id() { - let cursor = NotificationCursor { - created_at: Some("2026-03-20T10:02:00Z".to_string()), - notification_id: Some("n2".to_string()), - }; - - let url = build_notification_ws_url(&cursor, "client-connection-123").unwrap(); - let query = url.query().unwrap_or_default(); - - assert!(query.contains("since=2026-03-20T10%3A02%3A00Z")); - assert!(query.contains("replay_limit=500")); - assert!(query.contains("modpack_sync=true")); - assert!(query.contains("connection_id=client-connection-123")); - } - - #[test] - fn bootstrap_merge_collects_latest_changed_modpack_sync_per_resource() { - let mut state = NotificationRuntimeState::default(); - let invite = notification("n0", 0, false); - let sync_first = Notification { - notification_id: "n1".to_string(), - user_id: "u1".to_string(), - notification_type: Some("modpack_sync".to_string()), - resource_id: Some("modpack-1".to_string()), - message: "{\"notification_type\":\"modpack_sync\",\"simple_message\":\"hello\"}" - .to_string(), - created_at: "2026-03-20T10:01:00Z".to_string(), - created_at_unix: 1, - read: false, - }; - let sync_second = Notification { - notification_id: "n2".to_string(), - user_id: "u1".to_string(), - notification_type: Some("modpack_sync".to_string()), - resource_id: Some("modpack-1".to_string()), - message: "{\"notification_type\":\"modpack_sync\",\"simple_message\":\"updated\"}" - .to_string(), - created_at: "2026-03-20T10:02:00Z".to_string(), - created_at_unix: 2, - read: false, - }; - let sync_other = Notification { - notification_id: "n3".to_string(), - user_id: "u1".to_string(), - notification_type: Some("modpack_sync".to_string()), - resource_id: Some("modpack-2".to_string()), - message: "{\"notification_type\":\"modpack_sync\",\"simple_message\":\"other\"}" - .to_string(), - created_at: "2026-03-20T10:03:00Z".to_string(), - created_at_unix: 3, - read: false, - }; - - let outcome = merge_notifications_and_collect_updates( - &mut state, - vec![sync_second.clone(), invite, sync_first, sync_other.clone()], - ); - - assert!(outcome.notifications_for_ui.is_some()); - assert_eq!(outcome.modpack_sync_notifications.len(), 2); - assert_eq!(outcome.modpack_sync_notifications[0].notification_id, "n2"); - assert_eq!(outcome.modpack_sync_notifications[1].notification_id, "n3"); - } - - #[test] - fn bootstrap_merge_retries_unprocessed_modpack_sync_even_when_unchanged() { - let mut state = NotificationRuntimeState::default(); - let sync_notification = Notification { - notification_id: "n1".to_string(), - user_id: "u1".to_string(), - notification_type: Some("modpack_sync".to_string()), - resource_id: Some("modpack-1".to_string()), - message: "{\"notification_type\":\"modpack_sync\",\"simple_message\":\"updated\"}" - .to_string(), - created_at: "2026-03-20T10:02:00Z".to_string(), - created_at_unix: 2, - read: false, - }; - - let _ = merge_notifications_and_collect_updates(&mut state, vec![sync_notification.clone()]); - let outcome = merge_notifications_and_collect_updates(&mut state, vec![sync_notification.clone()]); - - assert!(outcome.notifications_for_ui.is_none()); - assert_eq!(outcome.modpack_sync_notifications.len(), 1); - assert_eq!(outcome.modpack_sync_notifications[0].notification_id, "n1"); - } - - #[test] - fn bootstrap_merge_skips_processed_modpack_sync_when_unchanged() { - let mut state = NotificationRuntimeState::default(); - let sync_notification = Notification { - notification_id: "n1".to_string(), - user_id: "u1".to_string(), - notification_type: Some("modpack_sync".to_string()), - resource_id: Some("modpack-1".to_string()), - message: "{\"notification_type\":\"modpack_sync\",\"simple_message\":\"updated\"}" - .to_string(), - created_at: "2026-03-20T10:02:00Z".to_string(), - created_at_unix: 2, - read: false, - }; - - let _ = merge_notifications_and_collect_updates(&mut state, vec![sync_notification.clone()]); - state.mark_modpack_sync_processed(&sync_notification); - let outcome = merge_notifications_and_collect_updates(&mut state, vec![sync_notification]); - - assert!(outcome.notifications_for_ui.is_none()); - assert!(outcome.modpack_sync_notifications.is_empty()); - } + app.state::() + .read_notification(notification_id) + .await + .map_err(tauri::Error::from) } diff --git a/src-tauri/src/account/mod.rs b/src-tauri/src/account/mod.rs index b46bf6bd..370ad977 100644 --- a/src-tauri/src/account/mod.rs +++ b/src-tauri/src/account/mod.rs @@ -1,24 +1,33 @@ +use quadrant_host::QuadrantHost; +use tauri::{AppHandle, Manager}; + pub mod id; pub mod quadrant_settings_sync; pub mod quadrant_share; pub mod quadrant_sync; -use crate::tauri_adapter::TauriSecretStore; - #[tauri::command] -pub fn set_secret(key: String, value: String) -> Result<(), tauri::Error> { - quadrant_core::account::set_secret(&TauriSecretStore, &key, &value).map_err(tauri::Error::from) +pub fn set_secret(key: String, value: String, app: AppHandle) -> Result<(), tauri::Error> { + app.state::() + .set_secret(key, value) + .map_err(tauri::Error::from) } pub fn get_account_token() -> Result { - quadrant_core::account::get_account_token(&TauriSecretStore) + Err(anyhow::anyhow!( + "account token access moved to quadrant-host" + )) } pub fn get_refresh_token() -> Result { - quadrant_core::account::get_refresh_token(&TauriSecretStore) + Err(anyhow::anyhow!( + "refresh token access moved to quadrant-host" + )) } #[tauri::command] -pub fn clear_account_token() -> Result<(), tauri::Error> { - quadrant_core::account::clear_account_token(&TauriSecretStore).map_err(tauri::Error::from) +pub fn clear_account_token(app: AppHandle) -> Result<(), tauri::Error> { + app.state::() + .clear_account_token() + .map_err(tauri::Error::from) } diff --git a/src-tauri/src/account/quadrant_settings_sync.rs b/src-tauri/src/account/quadrant_settings_sync.rs index 465554b2..95f0e45e 100644 --- a/src-tauri/src/account/quadrant_settings_sync.rs +++ b/src-tauri/src/account/quadrant_settings_sync.rs @@ -1,28 +1,18 @@ -use tauri::AppHandle; - -use crate::{ - mc_mod::get_user_agent, - tauri_adapter::{TauriSecretStore, TauriSettingsStore}, -}; +use quadrant_host::QuadrantHost; +use tauri::{AppHandle, Manager}; #[tauri::command] pub async fn get_quadrant_settings(app: AppHandle) -> Result<(), tauri::Error> { - quadrant_core::account::quadrant_settings_sync::get_quadrant_settings( - &TauriSettingsStore::new(app, "config.json"), - &TauriSecretStore, - &get_user_agent(), - ) - .await - .map_err(tauri::Error::from) + app.state::() + .get_quadrant_settings() + .await + .map_err(tauri::Error::from) } #[tauri::command] pub async fn submit_quadrant_settings(app: AppHandle) -> Result<(), tauri::Error> { - quadrant_core::account::quadrant_settings_sync::submit_quadrant_settings( - &TauriSettingsStore::new(app, "config.json"), - &TauriSecretStore, - &get_user_agent(), - ) - .await - .map_err(tauri::Error::from) + app.state::() + .submit_quadrant_settings() + .await + .map_err(tauri::Error::from) } diff --git a/src-tauri/src/account/quadrant_share.rs b/src-tauri/src/account/quadrant_share.rs index 152d64a8..cae1f90a 100644 --- a/src-tauri/src/account/quadrant_share.rs +++ b/src-tauri/src/account/quadrant_share.rs @@ -1,56 +1,39 @@ -use tauri::{AppHandle, Emitter}; -use tauri_plugin_clipboard_manager::ClipboardExt; +use quadrant_host::QuadrantHost; +use tauri::{AppHandle, Manager}; pub use quadrant_core::account::quadrant_share::{ QuadrantShareResponse, QuadrantShareSubmission, QuadrantShareSubmissionResponse, }; -use crate::{ - mc_mod::get_user_agent, - modpacks::general::{InstalledModpack, get_modpacks}, - tauri_adapter::{TauriSecretStore, TauriSettingsStore}, -}; - #[tauri::command] -pub async fn share_modpack(modpack_name: String, app: AppHandle) -> Result<(), tauri::Error> { - let modpacks = get_modpacks(false, app.clone()).await; - let modpack = modpacks - .iter() - .find(|modpack| modpack.name == modpack_name) - .ok_or_else(|| tauri::Error::from(anyhow::anyhow!("Modpack not found")))?; - share_modpack_raw(InstalledModpack::from(modpack.to_owned()), app).await +pub async fn share_modpack( + modpack_name: String, + app: AppHandle, +) -> Result { + app.state::() + .share_modpack(modpack_name) + .await + .map_err(tauri::Error::from) } #[tauri::command] pub async fn share_modpack_raw( - mod_config: InstalledModpack, + mod_config: crate::modpacks::general::InstalledModpack, app: AppHandle, -) -> Result<(), tauri::Error> { - let res = quadrant_core::account::quadrant_share::share_modpack_raw( - &TauriSettingsStore::new(app.clone(), "config.json"), - &TauriSecretStore, - &get_user_agent(), - mod_config, - env!("QUADRANT_API_KEY"), - ) - .await - .map_err(tauri::Error::from)?; - - crate::other::telemetry::send_telemetry(app.clone()).await; - app.emit("quadrantShareSubmission", &res)?; - app.clipboard() - .write_text(res.code.to_string()) - .map_err(|e| tauri::Error::from(anyhow::Error::from(e)))?; - Ok(()) +) -> Result { + app.state::() + .share_modpack_raw(mod_config) + .await + .map_err(tauri::Error::from) } #[tauri::command] -pub async fn get_quadrant_share_modpack(code: String) -> Result { - quadrant_core::account::quadrant_share::get_quadrant_share_modpack( - &get_user_agent(), - env!("QUADRANT_API_KEY"), - code, - ) - .await - .map_err(tauri::Error::from) +pub async fn get_quadrant_share_modpack( + code: String, + app: AppHandle, +) -> Result { + app.state::() + .get_quadrant_share_modpack(code) + .await + .map_err(tauri::Error::from) } diff --git a/src-tauri/src/account/quadrant_sync.rs b/src-tauri/src/account/quadrant_sync.rs index ed3f42a6..149fabcc 100644 --- a/src-tauri/src/account/quadrant_sync.rs +++ b/src-tauri/src/account/quadrant_sync.rs @@ -1,40 +1,30 @@ +use quadrant_host::QuadrantHost; use tauri::{AppHandle, Manager}; -use tokio::sync::Mutex; pub use quadrant_core::account::quadrant_sync::{ModpackOwner, SyncedModpack}; -use crate::{ - AppState, - mc_mod::get_user_agent, - modpacks::general::LocalModpack, - tauri_adapter::{TauriSecretStore, mc_folder}, -}; - #[tauri::command] pub async fn get_synced_modpacks( show_owners: bool, modpack_id: Option, + app: AppHandle, ) -> Result, tauri::Error> { - quadrant_core::account::quadrant_sync::get_synced_modpacks( - &TauriSecretStore, - &get_user_agent(), - show_owners, - modpack_id, - ) - .await - .map_err(tauri::Error::from) + app.state::() + .get_synced_modpacks(show_owners, modpack_id) + .await + .map_err(tauri::Error::from) } #[tauri::command] -pub async fn kick_member(modpack_id: String, username: String) -> Result<(), tauri::Error> { - quadrant_core::account::quadrant_sync::kick_member( - &TauriSecretStore, - &get_user_agent(), - modpack_id, - username, - ) - .await - .map_err(tauri::Error::from) +pub async fn kick_member( + modpack_id: String, + username: String, + app: AppHandle, +) -> Result<(), tauri::Error> { + app.state::() + .kick_member(modpack_id, username) + .await + .map_err(tauri::Error::from) } #[tauri::command] @@ -42,27 +32,20 @@ pub async fn invite_member( modpack_id: String, username: String, admin: bool, + app: AppHandle, ) -> Result<(), tauri::Error> { - quadrant_core::account::quadrant_sync::invite_member( - &TauriSecretStore, - &get_user_agent(), - modpack_id, - username, - admin, - ) - .await - .map_err(tauri::Error::from) + app.state::() + .invite_member(modpack_id, username, admin) + .await + .map_err(tauri::Error::from) } #[tauri::command] -pub async fn delete_synced_modpack(modpack_id: String) -> Result<(), tauri::Error> { - quadrant_core::account::quadrant_sync::delete_synced_modpack( - &TauriSecretStore, - &get_user_agent(), - modpack_id, - ) - .await - .map_err(tauri::Error::from) +pub async fn delete_synced_modpack(modpack_id: String, app: AppHandle) -> Result<(), tauri::Error> { + app.state::() + .delete_synced_modpack(modpack_id) + .await + .map_err(tauri::Error::from) } #[tauri::command] @@ -71,27 +54,10 @@ pub async fn sync_modpack( overwrite: bool, app: AppHandle, ) -> Result<(), tauri::Error> { - let connection_id = { - let state = app.state::>(); - let state = state.lock().await; - state.notification_connection_id.clone() - }; - - let timestamp = quadrant_core::account::quadrant_sync::sync_modpack( - &TauriSecretStore, - &get_user_agent(), - modpack.clone(), - overwrite, - Some(connection_id.as_str()), - ) - .await - .map_err(tauri::Error::from)?; - - let persisted_modpack_id = - choose_persisted_modpack_id(&modpack, resolve_submitted_modpack_id(&modpack, timestamp).await?); - persist_sync_metadata(&app, &modpack.name, timestamp as u64, persisted_modpack_id.as_deref()) - .map_err(tauri::Error::from)?; - Ok(()) + app.state::() + .sync_modpack(modpack, overwrite) + .await + .map_err(tauri::Error::from) } #[tauri::command] @@ -101,109 +67,8 @@ pub async fn answer_invite( answer: bool, app: AppHandle, ) -> Result<(), tauri::Error> { - quadrant_core::account::quadrant_sync::answer_invite( - &TauriSecretStore, - &get_user_agent(), - modpack_id, - answer, - ) - .await - .map_err(tauri::Error::from)?; - super::id::read_notification(notification_id, app).await -} - -async fn resolve_submitted_modpack_id( - modpack: &LocalModpack, - timestamp: i64, -) -> Result, tauri::Error> { - let synced_modpacks = quadrant_core::account::quadrant_sync::get_synced_modpacks( - &TauriSecretStore, - &get_user_agent(), - false, - None, - ) - .await - .map_err(tauri::Error::from)?; - - let mut matching = synced_modpacks.into_iter().filter(|synced_modpack| { - synced_modpack.name == modpack.name - && synced_modpack.minecraft_version == modpack.version - && synced_modpack.mod_loader == modpack.mod_loader - && synced_modpack.last_synced == timestamp - }); - - let first = matching.next().map(|modpack| modpack.modpack_id); - if matching.next().is_some() { - return Ok(None); - } - - Ok(first) -} - -fn choose_persisted_modpack_id( - modpack: &LocalModpack, - resolved_modpack_id: Option, -) -> Option { - resolved_modpack_id.or_else(|| modpack.modpack_id.clone()) -} - -pub fn persist_sync_metadata( - app: &AppHandle, - modpack_name: &str, - last_synced: u64, - modpack_id: Option<&str>, -) -> Result<(), anyhow::Error> { - let modpack_folder = mc_folder(app)?.join("modpacks").join(modpack_name); - if !modpack_folder.exists() { - return Ok(()); - } - - quadrant_core::modpacks::set_modpack_sync_date( - &mc_folder(app)?, - last_synced, - modpack_name, - modpack_id, - ) -} - -#[cfg(test)] -mod tests { - use super::choose_persisted_modpack_id; - use crate::modpacks::general::LocalModpack; - use quadrant_core::models::{InstalledMod, ModLoader, ModSource}; - - fn local_modpack(modpack_id: Option<&str>) -> LocalModpack { - LocalModpack { - name: "Better Create".to_string(), - version: "1.20.1".to_string(), - mod_loader: ModLoader::Fabric, - mods: vec![InstalledMod { - id: "abc".to_string(), - source: ModSource::Modrinth, - download_url: "https://example.com/mod.jar".to_string(), - }], - unknown_mods: false, - is_applied: false, - last_synced: 0, - modpack_id: modpack_id.map(ToOwned::to_owned), - } - } - - #[test] - fn choose_persisted_modpack_id_keeps_existing_id_when_lookup_is_empty() { - let modpack = local_modpack(Some("existing-modpack-id")); - - let chosen = choose_persisted_modpack_id(&modpack, None); - - assert_eq!(chosen.as_deref(), Some("existing-modpack-id")); - } - - #[test] - fn choose_persisted_modpack_id_prefers_resolved_id() { - let modpack = local_modpack(Some("existing-modpack-id")); - - let chosen = choose_persisted_modpack_id(&modpack, Some("resolved-modpack-id".to_string())); - - assert_eq!(chosen.as_deref(), Some("resolved-modpack-id")); - } + app.state::() + .answer_invite(modpack_id, notification_id, answer) + .await + .map_err(tauri::Error::from) } diff --git a/src-tauri/src/config/mod.rs b/src-tauri/src/config/mod.rs index 6494d986..3ce84f22 100644 --- a/src-tauri/src/config/mod.rs +++ b/src-tauri/src/config/mod.rs @@ -1,17 +1,21 @@ +use quadrant_host::QuadrantHost; use tauri::AppHandle; +use tauri::Manager; pub use quadrant_core::config::{get_config_dir, get_mc_folder}; -use crate::tauri_adapter::TauriSettingsStore; - #[tauri::command] -pub fn get_minecraft_folder() -> Result { - let path = get_mc_folder().map_err(tauri::Error::from)?.unwrap(); +pub fn get_minecraft_folder(app: AppHandle) -> Result { + let path = app + .state::() + .get_minecraft_folder() + .map_err(tauri::Error::from)?; Ok(path.to_string_lossy().to_string()) } #[tauri::command] pub fn init_config(app: AppHandle) -> Result<(), tauri::Error> { - let store = TauriSettingsStore::new(app, "config.json"); - quadrant_core::config::ensure_default_app_config(&store).map_err(tauri::Error::from) + app.state::() + .init_config() + .map_err(tauri::Error::from) } diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index fa83159c..f4fbf3f4 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -1,4 +1,5 @@ -use config::init_config; +use quadrant_host::{QuadrantHost, QuadrantHostOptions}; +use std::path::PathBuf; use tauri::{ Emitter, Manager, menu::{Menu, MenuItem}, @@ -14,8 +15,6 @@ use tauri_plugin_deep_link::DeepLinkExt; use tauri_plugin_store::StoreExt; #[cfg(feature = "updater")] use tauri_plugin_updater::UpdaterExt; -#[cfg(feature = "quadrant_id")] -use uuid::Uuid; #[allow(dead_code)] // This is used in the Quadrant ID feature pub(crate) const QNT_BASE_URL: &str = "https://api.usequadrant.dev/api/v3"; @@ -35,10 +34,25 @@ pub struct AppState { pub is_update_enabled: bool, pub update: Option, pub update_bytes: Vec, - #[cfg(feature = "quadrant_id")] - pub notification_connection_id: String, - #[cfg(feature = "quadrant_id")] - pub notification_state: account::id::NotificationRuntimeState, +} + +fn build_quadrant_host(app: &tauri::AppHandle) -> Result { + let data_dir = app + .path() + .app_data_dir() + .ok() + .or_else(|| quadrant_core::config::get_config_dir().ok().flatten()) + .unwrap_or_else(|| PathBuf::from(".")); + let mut options = QuadrantHostOptions::new( + data_dir, + env!("QUADRANT_OAUTH2_CLIENT_ID"), + env!("QUADRANT_OAUTH2_CLIENT_SECRET"), + env!("QUADRANT_API_KEY"), + ); + options.api_base_url = std::env::var("QUADRANT_API_BASE_URL").ok(); + options.app_version = app.package_info().version.to_string(); + options.os_name = tauri_plugin_os::platform().to_string().to_uppercase(); + QuadrantHost::new(options) } #[cfg_attr(mobile, tauri::mobile_entry_point)] @@ -48,14 +62,10 @@ pub async fn run() { log::info!("Initializing Tauri..."); let mut builder = tauri::Builder::default().manage(Mutex::new(AppState { - is_update_enabled: false, updated_modpacks: vec![], + is_update_enabled: false, update: None, update_bytes: vec![], - #[cfg(feature = "quadrant_id")] - notification_connection_id: Uuid::now_v7().to_string(), - #[cfg(feature = "quadrant_id")] - notification_state: account::id::NotificationRuntimeState::default(), })); #[cfg(desktop)] @@ -105,7 +115,23 @@ pub async fn run() { .plugin(tauri_plugin_opener::init()) .plugin(tauri_plugin_clipboard_manager::init()) .setup(|app| { - init_config(app.handle().clone())?; + let host = build_quadrant_host(&app.handle().clone())?; + let mut host_events = host.subscribe_events(); + let app_handle = app.handle().clone(); + tauri::async_runtime::spawn(async move { + loop { + match host_events.recv().await { + Ok(event) => { + let _ = app_handle.emit(&event.event, event.payload.clone()); + } + Err(tokio::sync::broadcast::error::RecvError::Lagged(skipped)) => { + log::warn!("Dropped {skipped} host events while forwarding to Tauri"); + } + Err(tokio::sync::broadcast::error::RecvError::Closed) => break, + } + } + }); + app.manage(host.clone()); log::info!("Initializing app...\nInitializing config..."); let mut autoupdate = true; @@ -207,18 +233,21 @@ pub async fn run() { } #[cfg(feature = "telemetry")] { - let handle = app.handle().clone(); log::info!("Initializing telemetry..."); + let host = host.clone(); tauri::async_runtime::spawn(async move { - other::telemetry::send_telemetry(handle.clone().to_owned()).await; + let _ = host.send_telemetry().await; }); } #[cfg(feature = "quadrant_id")] { log::info!("Starting Quadrant notification and sync workers..."); - let app_handle = app.handle().clone(); - account::id::start_notification_worker(app_handle.clone()); - account::id::start_settings_sync_worker(app_handle); + let worker_host = host.clone(); + tauri::async_runtime::spawn(async move { + if let Err(error) = worker_host.start_background_workers().await { + log::error!("Failed to start Quadrant host workers: {error}"); + } + }); } log::info!("Initializing tray..."); let tray = app.tray_by_id("main"); @@ -266,8 +295,9 @@ pub async fn run() { } log::info!("Updating Minecraft versions..."); + let version_host = host.clone(); let _task = tokio::task::spawn(async move { - let _res = mc_mod::get_versions().await; + let _res = version_host.get_versions().await; match _res { Ok(_) => {} Err(e) => { @@ -285,10 +315,12 @@ pub async fn run() { modpacks::manage_modpack::update_modpack, modpacks::manage_modpack::create_modpack, modpacks::manage_modpack::delete_modpack, + modpacks::manage_modpack::get_modpacks_folder, modpacks::manage_modpack::open_modpacks_folder, modpacks::manage_modpack::register_mod, modpacks::general::install_modpack, modpacks::general::export_modpack, + modpacks::general::export_modpack_to, mc_mod::modrinth::get_mod_modrinth, mc_mod::search_mods, mc_mod::get_versions, @@ -347,6 +379,10 @@ pub async fn run() { account::quadrant_sync::answer_invite, #[cfg(feature = "quadrant_id")] account::quadrant_share::get_quadrant_share_modpack, + #[cfg(feature = "quadrant_id")] + account::quadrant_settings_sync::get_quadrant_settings, + #[cfg(feature = "quadrant_id")] + account::quadrant_settings_sync::submit_quadrant_settings, ]); builder diff --git a/src-tauri/src/mc_mod/curseforge.rs b/src-tauri/src/mc_mod/curseforge.rs index 78abb3dd..4fdaa2ec 100644 --- a/src-tauri/src/mc_mod/curseforge.rs +++ b/src-tauri/src/mc_mod/curseforge.rs @@ -1,24 +1,33 @@ +use quadrant_host::QuadrantHost; +use tauri::{AppHandle, Manager}; + use crate::mc_mod::{GetModArgs, Mod}; pub use quadrant_core::mc_mod::curseforge::ModFile; #[tauri::command] -pub async fn get_mod_curseforge(args: GetModArgs) -> Result { - quadrant_core::mc_mod::curseforge::get_mod_curseforge(args) +pub async fn get_mod_curseforge(args: GetModArgs, app: AppHandle) -> Result { + app.state::() + .get_mod_curseforge(args) .await .map_err(tauri::Error::from) } #[tauri::command] -pub async fn get_mod_owners_curseforge(id: String) -> Result, tauri::Error> { - quadrant_core::mc_mod::curseforge::get_mod_owners_curseforge(id) +pub async fn get_mod_owners_curseforge( + id: String, + app: AppHandle, +) -> Result
Add Electron support as an alternative runtime
Dependency updates