diff --git a/.github/workflows/beta.yml b/.github/workflows/beta.yml index 76b729bd..54700c0b 100644 --- a/.github/workflows/beta.yml +++ b/.github/workflows/beta.yml @@ -38,7 +38,7 @@ jobs: - name: Compute metadata (date, short SHA) id: meta - uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 with: script: | const date = new Date().toISOString().slice(0, 10); @@ -47,7 +47,7 @@ jobs: core.setOutput('date', date); - name: Setup Node - uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 + uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 with: node-version: '24' cache: 'npm' @@ -59,6 +59,12 @@ jobs: - name: Build frontend working-directory: Frontend + env: + VITE_SENTRY_ENVIRONMENT: beta + VITE_SENTRY_RELEASE: openvcs-beta-${{ steps.meta.outputs.short_sha }} + SENTRY_AUTH_TOKEN: ${{ matrix.platform == 'ubuntu-24.04' && secrets.SENTRY_AUTH_TOKEN || '' }} + SENTRY_ORG: ${{ matrix.platform == 'ubuntu-24.04' && secrets.SENTRY_ORG || '' }} + SENTRY_PROJECT: ${{ matrix.platform == 'ubuntu-24.04' && secrets.SENTRY_PROJECT || '' }} run: npm run build - name: Install Rust (stable) @@ -89,7 +95,7 @@ jobs: run: cargo clippy --all-targets -- -D warnings - name: Remove existing 'openvcs-beta' release & tag - uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 with: script: | const owner = context.repo.owner; @@ -115,6 +121,8 @@ jobs: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} FRONTEND_SKIP_BUILD: '1' OPENVCS_UPDATE_CHANNEL: beta + OPENVCS_SENTRY_DSN: ${{ secrets.OPENVCS_SENTRY_DSN }} + OPENVCS_SENTRY_ENVIRONMENT: beta TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_PRIVATE_KEY }} TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_PRIVATE_KEY_PASSWORD }} with: diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 348cc1bc..7709ad8e 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -53,7 +53,7 @@ jobs: fetch-depth: 0 - name: Initialize CodeQL - uses: github/codeql-action/init@c10b8064de6f491fea524254123dbe5e09572f13 # v3 + uses: github/codeql-action/init@95e58e9a2cdfd71adc6e0353d5c52f41a045d225 # v3 with: languages: ${{ matrix.language }} build-mode: ${{ matrix.build-mode }} @@ -64,9 +64,9 @@ jobs: - name: Autobuild if: matrix.build-mode == 'autobuild' - uses: github/codeql-action/autobuild@c10b8064de6f491fea524254123dbe5e09572f13 # v3 + uses: github/codeql-action/autobuild@95e58e9a2cdfd71adc6e0353d5c52f41a045d225 # v3 - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@c10b8064de6f491fea524254123dbe5e09572f13 # v3 + uses: github/codeql-action/analyze@95e58e9a2cdfd71adc6e0353d5c52f41a045d225 # v3 with: category: "/language:${{ matrix.language }}" diff --git a/.github/workflows/development.yml b/.github/workflows/development.yml index e7ace271..9ca9e435 100644 --- a/.github/workflows/development.yml +++ b/.github/workflows/development.yml @@ -35,6 +35,7 @@ jobs: RUSTC_WRAPPER: sccache SCCACHE_GHA_ENABLED: ${{ vars.SSCCACHE_GHA_ENABLED }} SCCACHE_CACHE_SIZE: ${{ vars.SSCCACHE_SIZE }} + OPENVCS_UPDATE_CHANNEL: dev timeout-minutes: 20 steps: @@ -59,7 +60,7 @@ jobs: # ---------- Frontend: lint + typecheck ---------- - name: Setup Node - uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 + uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 with: node-version: '24' cache: 'npm' diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index a2f67423..6fbb9e2a 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -101,7 +101,7 @@ jobs: - name: Compute metadata (date, short SHA, compare, changelog) id: meta - uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 with: script: | const date = new Date().toISOString().slice(0, 10); // UTC date @@ -152,7 +152,7 @@ jobs: # ---------- Frontend ---------- - name: Setup Node - uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 + uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 with: node-version: '24' cache: 'npm' @@ -164,6 +164,12 @@ jobs: - name: Build frontend working-directory: Frontend + env: + VITE_SENTRY_ENVIRONMENT: nightly + VITE_SENTRY_RELEASE: openvcs-nightly-${{ steps.meta.outputs.short_sha }} + SENTRY_AUTH_TOKEN: ${{ matrix.platform == 'ubuntu-24.04' && secrets.SENTRY_AUTH_TOKEN || '' }} + SENTRY_ORG: ${{ matrix.platform == 'ubuntu-24.04' && secrets.SENTRY_ORG || '' }} + SENTRY_PROJECT: ${{ matrix.platform == 'ubuntu-24.04' && secrets.SENTRY_PROJECT || '' }} run: npm run build # ---------- Rust & platform deps ---------- @@ -197,7 +203,7 @@ jobs: # ---------- Reset rolling tag (optional but keeps things tidy) ---------- - name: Remove existing 'openvcs-nightly' release & tag (if any) - uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 with: script: | const owner = context.repo.owner; @@ -224,6 +230,8 @@ jobs: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} FRONTEND_SKIP_BUILD: '1' OPENVCS_UPDATE_CHANNEL: nightly + OPENVCS_SENTRY_DSN: ${{ secrets.OPENVCS_SENTRY_DSN }} + OPENVCS_SENTRY_ENVIRONMENT: nightly TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_PRIVATE_KEY }} TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_PRIVATE_KEY_PASSWORD }} with: diff --git a/.github/workflows/opencode-review.yml b/.github/workflows/opencode-review.yml index 3e2c7ff8..6fabd629 100644 --- a/.github/workflows/opencode-review.yml +++ b/.github/workflows/opencode-review.yml @@ -26,10 +26,10 @@ jobs: env: OPENCODE_API_KEY: ${{ secrets.ZEN_API_KEY }} OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + #GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: model: ${{ vars.OPENCODE_REVIEW_MODEL }} - use_github_token: true + use_github_token: false prompt: | Review this pull request: - Check for code quality issues @@ -37,17 +37,17 @@ jobs: - Suggest improvements - name: fallback - if: steps.review_primary.outcome == 'failure' + if: ${{ steps.review_primary.outcome == 'failure' }} uses: anomalyco/opencode/github@2410593023d2c61f05123c9b0faf189a28dfbeee env: OPENCODE_API_KEY: ${{ secrets.ZEN_API_KEY }} OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + #GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: model: ${{ vars.OPENCODE_REVIEW_MODEL_FALLBACK }} - use_github_token: true + use_github_token: false prompt: | Review this pull request: - Check for code quality issues - Look for potential bugs - - Suggest improvements \ No newline at end of file + - Suggest improvements diff --git a/.github/workflows/publish-stable.yml b/.github/workflows/publish-stable.yml index 6440e18a..61ea930f 100644 --- a/.github/workflows/publish-stable.yml +++ b/.github/workflows/publish-stable.yml @@ -45,7 +45,7 @@ jobs: # ---------- Frontend (Node + Vite) ---------- - name: Setup Node - uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 + uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 with: node-version: '24' cache: 'npm' @@ -57,6 +57,12 @@ jobs: - name: Build frontend working-directory: Frontend + env: + VITE_SENTRY_ENVIRONMENT: stable + VITE_SENTRY_RELEASE: ${{ steps.version.outputs.tag }} + SENTRY_AUTH_TOKEN: ${{ matrix.platform == 'ubuntu-24.04' && secrets.SENTRY_AUTH_TOKEN || '' }} + SENTRY_ORG: ${{ matrix.platform == 'ubuntu-24.04' && secrets.SENTRY_ORG || '' }} + SENTRY_PROJECT: ${{ matrix.platform == 'ubuntu-24.04' && secrets.SENTRY_PROJECT || '' }} run: npm run build # ---------- Rust toolchain & deps ---------- @@ -144,6 +150,8 @@ jobs: # We already built the Frontend; tell Backend/Tauri to skip any beforeBuildCommand FRONTEND_SKIP_BUILD: '1' OPENVCS_UPDATE_CHANNEL: stable + OPENVCS_SENTRY_DSN: ${{ secrets.OPENVCS_SENTRY_DSN }} + OPENVCS_SENTRY_ENVIRONMENT: stable # Stable production builds should report the plain package version (not git metadata). OPENVCS_OFFICIAL_RELEASE: '1' TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_PRIVATE_KEY }} diff --git a/.gitignore b/.gitignore index fff1e6ec..5cc6ebed 100644 --- a/.gitignore +++ b/.gitignore @@ -27,3 +27,6 @@ dist-ssr /Backend/icons/ios /Core .env.tauri.local +.env +.opencode/ +/openvcs.plugins.local.json diff --git a/.opencode/package-lock.json b/.opencode/package-lock.json deleted file mode 100644 index 6aa38cb4..00000000 --- a/.opencode/package-lock.json +++ /dev/null @@ -1,115 +0,0 @@ -{ - "name": ".opencode", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "dependencies": { - "@opencode-ai/plugin": "1.3.15" - } - }, - "node_modules/@opencode-ai/plugin": { - "version": "1.3.15", - "resolved": "https://registry.npmjs.org/@opencode-ai/plugin/-/plugin-1.3.15.tgz", - "integrity": "sha512-jZJbuvUXc5Limz8pacQl+ffATjjKGlq+xaA4wTUeW+/spwOf7Yr5Ryyvan8eNlYM8wy6h5SLfznl1rlFpjYC8w==", - "license": "MIT", - "dependencies": { - "@opencode-ai/sdk": "1.3.15", - "zod": "4.1.8" - }, - "peerDependencies": { - "@opentui/core": ">=0.1.96", - "@opentui/solid": ">=0.1.96" - }, - "peerDependenciesMeta": { - "@opentui/core": { - "optional": true - }, - "@opentui/solid": { - "optional": true - } - } - }, - "node_modules/@opencode-ai/sdk": { - "version": "1.3.15", - "resolved": "https://registry.npmjs.org/@opencode-ai/sdk/-/sdk-1.3.15.tgz", - "integrity": "sha512-Uk59C7wsK20wpdr277yx7Xz7TqG5jGqlZUpSW3wDH/7a2K2iBg0lXc2wskHuCXLRXMhXpPZtb4a3SOpPENkkbg==", - "license": "MIT", - "dependencies": { - "cross-spawn": "7.0.6" - } - }, - "node_modules/cross-spawn": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", - "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", - "license": "MIT", - "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "license": "ISC" - }, - "node_modules/path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "license": "MIT", - "dependencies": { - "shebang-regex": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "license": "ISC", - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/zod": { - "version": "4.1.8", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/colinhacks" - } - } - } -} diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index 568b198e..0306417c 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -57,6 +57,17 @@ Backend: - State lifecycle: Startup config load, optional reopen-last-repo, runtime config updates. +- Monitoring: + Optional Sentry reporting is initialized separately in `Backend/src/monitoring.rs` and + `Frontend/src/scripts/lib/monitoring.ts`, with backend/frontend events gated by + `general.crash_reports`. The frontend captures errors and relays them to a + backend-owned Sentry client over Tauri IPC, while backend Sentry uses runtime + process env values with a build-time embedded fallback for packaged builds. + Recent frontend console output is retained as breadcrumbs and attached to + frontend monitoring events. Backend Rust `log` records are bridged into + Sentry without replacing the existing console/file logger: `error!` records + produce Sentry events, `warn!` records become breadcrumbs/logs, and `info!` + records become breadcrumbs when crash reporting is enabled. - Plugin lifecycle: Built-in/user plugin discovery, config-driven sync, install/uninstall, and approval gating. - Reliability: diff --git a/Backend/Cargo.toml b/Backend/Cargo.toml index 1be6664b..96c99654 100644 --- a/Backend/Cargo.toml +++ b/Backend/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "openvcs" -version = "0.2.2" +version = "0.3.0" description = "OpenVCS: a lightweight, highly customizable Git GUI built on Rust & Tauri" authors = ["Jordon Brooks"] homepage = "https://bbgames.dev" @@ -18,6 +18,7 @@ name = "openvcs_lib" crate-type = ["staticlib", "cdylib", "rlib"] [build-dependencies] +dotenvy = "0.15" tauri-build = { version = "2.4", default-features = false, features = [] } serde = { version = "1", features = ["derive"] } serde_json = "1.0" @@ -26,6 +27,7 @@ serde_json = "1.0" default = [] [dependencies] +dotenvy = "0.15" tauri = { version = "2.10", features = [] } tauri-plugin-opener = "2.5" serde = { version = "1", features = ["derive"] } @@ -35,9 +37,11 @@ tauri-plugin-updater = "2.10" tokio = { version = "1.51", features = ["io-util", "process", "rt", "sync", "time"] } dirs = "6" regex = "1.12" -log = "0.4" +log = "0.4" env_logger = "0.11" parking_lot = "0.12" +sentry = { version = "0.46", default-features = false, features = ["backtrace", "contexts", "panic", "reqwest", "rustls", "log", "logs"] } +sentry-log = { version = "0.46", features = ["logs"] } toml = "1.0.6" directories = "6" serde_json = "1.0" diff --git a/Backend/build.rs b/Backend/build.rs index 0cf32e86..6df5596f 100644 --- a/Backend/build.rs +++ b/Backend/build.rs @@ -1,8 +1,25 @@ // Copyright © 2025-2026 OpenVCS Contributors // SPDX-License-Identifier: GPL-3.0-or-later +use dotenvy::from_path_iter; use serde::Deserialize; use std::{env, fs, path::PathBuf, process::Command}; +/// Loads `Client/.env` into the build environment without overriding existing vars. +fn load_local_dotenv(manifest_dir: &std::path::Path) { + let dotenv_path = manifest_dir.join("../.env"); + println!("cargo:rerun-if-changed={}", dotenv_path.display()); + + let Ok(iter) = from_path_iter(&dotenv_path) else { + return; + }; + + for (key, value) in iter.flatten() { + if env::var_os(&key).is_none() { + env::set_var(key, value); + } + } +} + fn load_channel_metadata() -> ChannelMetadata { let manifest_dir = PathBuf::from(env::var("CARGO_MANIFEST_DIR").expect("CARGO_MANIFEST_DIR")); let config_path = manifest_dir.join("../channel-metadata.json"); @@ -220,6 +237,7 @@ fn ensure_generated_node_runtime_resource_dir(manifest_dir: &std::path::Path) { fn main() { // Base config path (in the Backend crate) let manifest_dir = PathBuf::from(env::var("CARGO_MANIFEST_DIR").expect("CARGO_MANIFEST_DIR")); + load_local_dotenv(&manifest_dir); let base = manifest_dir.join("tauri.conf.json"); let data = fs::read_to_string(&base).expect("read tauri.conf.json"); @@ -378,6 +396,12 @@ fn main() { println!("cargo:rustc-env=OPENVCS_VERSION={}", version); println!("cargo:rustc-env=OPENVCS_BUILD={}", build_id); + if let Ok(value) = env::var("OPENVCS_SENTRY_DSN") { + println!("cargo:rustc-env=OPENVCS_SENTRY_DSN_BUILT={}", value); + } + if let Ok(value) = env::var("OPENVCS_SENTRY_ENVIRONMENT") { + println!("cargo:rustc-env=OPENVCS_SENTRY_ENVIRONMENT_BUILT={}", value); + } ensure_generated_builtins_resource_dir(&manifest_dir); ensure_generated_node_runtime_resource_dir(&manifest_dir); diff --git a/Backend/openvcs-git-plugin-0.1.0.tgz b/Backend/openvcs-git-plugin-0.1.0.tgz deleted file mode 100644 index 1ceb42d1..00000000 Binary files a/Backend/openvcs-git-plugin-0.1.0.tgz and /dev/null differ diff --git a/Backend/scripts/ensure-built-in-plugins.js b/Backend/scripts/ensure-built-in-plugins.js index 16822542..58a88169 100644 --- a/Backend/scripts/ensure-built-in-plugins.js +++ b/Backend/scripts/ensure-built-in-plugins.js @@ -9,6 +9,7 @@ const scriptDir = __dirname; const backendDir = path.resolve(scriptDir, '..'); const clientDir = path.resolve(backendDir, '..'); const builtInConfigPath = path.join(clientDir, 'openvcs.plugins.json'); +const localConfigPath = path.join(clientDir, 'openvcs.plugins.local.json'); const builtInOutputDir = path.join(clientDir, 'target', 'openvcs', 'built-in-plugins'); const nodeRuntimeDir = path.join(clientDir, 'target', 'openvcs', 'node-runtime'); const npmExecutable = process.platform === 'win32' ? 'npm.cmd' : 'npm'; @@ -79,13 +80,78 @@ function ensureBundledNodeRuntime() { console.log(`Bundled node runtime -> ${dest}`); } +/** + * Determines the active update channel from environment variable. + * Supports stable/beta/dev and maps nightly->dev. + * Defaults to stable when unset. + */ +function getActiveChannel() { + const channel = (process.env.OPENVCS_UPDATE_CHANNEL || '').trim().toLowerCase(); + if (channel === 'nightly') { + return 'dev'; + } + if (channel === 'stable' || channel === 'beta' || channel === 'dev') { + return channel; + } + return 'stable'; +} + +/** + * Reads the built-in plugin config and extracts specs for the active channel. + * Falls back to stable if channel array is missing. + */ function readBuiltInConfig() { const raw = fs.readFileSync(builtInConfigPath, 'utf8'); const parsed = JSON.parse(raw); - const entries = Array.isArray(parsed?.plugin) ? parsed.plugin : []; + const channel = getActiveChannel(); + + // Try the channel-specific array first, fall back to stable + let entries = []; + if (Array.isArray(parsed?.[channel])) { + entries = parsed[channel]; + } else if (Array.isArray(parsed?.stable)) { + console.warn(`Channel '${channel}' not found in config, falling back to 'stable'`); + entries = parsed.stable; + } + return entries.map((value) => String(value || '').trim()).filter(Boolean); } +/** + * Reads local override config and returns the channel-specific override when present. + * Returns null when no override applies for the active channel. + */ +function readLocalOverrideConfig() { + if (!fs.existsSync(localConfigPath)) { + return null; + } + + const raw = fs.readFileSync(localConfigPath, 'utf8'); + const parsed = JSON.parse(raw); + const channel = getActiveChannel(); + + if (!Object.prototype.hasOwnProperty.call(parsed || {}, channel)) { + return null; + } + + const entries = Array.isArray(parsed?.[channel]) ? parsed[channel] : []; + return entries.map((value) => String(value || '').trim()).filter(Boolean); +} + +/** + * Determines which plugin specs to use: local override takes priority + * when present for the active channel, otherwise uses main config. + */ +function resolvePluginSpecs() { + const localSpecs = readLocalOverrideConfig(); + if (localSpecs !== null) { + const channel = getActiveChannel(); + console.log(`Using local override for channel '${channel}' (${localSpecs.length} plugins)`); + return localSpecs; + } + return readBuiltInConfig(); +} + function resolveLocalSource(spec) { if (!spec) return null; let candidate = spec; @@ -224,8 +290,9 @@ function rebuildBuiltInPlugins() { fs.rmSync(builtInOutputDir, { recursive: true, force: true }); ensureDirectory(builtInOutputDir); - const specs = readBuiltInConfig(); - console.log(`Syncing ${specs.length} built-in plugins from ${builtInConfigPath}`); + const specs = resolvePluginSpecs(); + const channel = getActiveChannel(); + console.log(`Syncing ${specs.length} built-in plugins for channel '${channel}' from ${builtInConfigPath}`); for (const spec of specs) { const { tempRoot, packageDir, pluginId } = stageBuiltInPlugin(spec); diff --git a/Backend/scripts/run-tauri-before-command.js b/Backend/scripts/run-tauri-before-command.js index 0aab8422..164bb774 100644 --- a/Backend/scripts/run-tauri-before-command.js +++ b/Backend/scripts/run-tauri-before-command.js @@ -25,8 +25,11 @@ if (dryRun) { process.exit(0); } -function run(cmd, args, cwd) { +function run(cmd, args, cwd, env) { const spawnOpts = { cwd, stdio: 'inherit' }; + if (env) { + spawnOpts.env = { ...process.env, ...env }; + } if ( process.platform === 'win32' && (cmd === 'npm' || cmd.toLowerCase().endsWith('.cmd') || cmd.toLowerCase().endsWith('.bat')) @@ -44,7 +47,10 @@ function run(cmd, args, cwd) { } } -run(process.execPath, [ensureScript], backendDir); +const ensureEnv = mode === 'dev' && !process.env.OPENVCS_UPDATE_CHANNEL + ? { OPENVCS_UPDATE_CHANNEL: 'dev' } + : null; +run(process.execPath, [ensureScript], backendDir, ensureEnv); if (mode === 'build' && process.env.FRONTEND_SKIP_BUILD === '1') { console.log('FRONTEND_SKIP_BUILD=1; skipping Frontend build step.'); diff --git a/Backend/src/core/mod.rs b/Backend/src/core/mod.rs index ceab45f1..2dd70b3b 100644 --- a/Backend/src/core/mod.rs +++ b/Backend/src/core/mod.rs @@ -97,6 +97,8 @@ pub trait Vcs: Send + Sync { /// Stages changes represented by a textual patch. fn stage_patch(&self, patch: &str) -> Result<()>; + /// Stages explicit paths to the index. + fn stage_paths(&self, paths: &[PathBuf]) -> Result<()>; /// Discards changes for the provided repository-relative paths. fn discard_paths(&self, paths: &[PathBuf]) -> Result<()>; /// Applies a patch in reverse to revert its changes. diff --git a/Backend/src/core/ui.rs b/Backend/src/core/ui.rs index 163133e2..45ee53d2 100644 --- a/Backend/src/core/ui.rs +++ b/Backend/src/core/ui.rs @@ -5,6 +5,16 @@ use serde::{Deserialize, Serialize}; +/// Menu surface target for rendering. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "kebab-case")] +pub enum MenuSurface { + /// Menu appears in the main menubar. + Menubar, + /// Menu appears in the settings modal. + Settings, +} + /// Menu descriptor contributed by a plugin. #[derive(Debug, Clone, Serialize, Deserialize)] pub struct Menu { @@ -15,6 +25,8 @@ pub struct Menu { /// Optional display ordering hint. #[serde(default, skip_serializing_if = "Option::is_none")] pub order: Option, + /// Required render target surface ('menubar' or 'settings'). + pub surface: MenuSurface, /// Renderable UI elements under the menu. pub elements: Vec, } diff --git a/Backend/src/lib.rs b/Backend/src/lib.rs index 7fb119c8..5dc52c7b 100644 --- a/Backend/src/lib.rs +++ b/Backend/src/lib.rs @@ -5,7 +5,7 @@ //! This crate wires together Tauri command handlers, runtime state, //! plugin discovery, and startup behavior. -use log::warn; +use log::{error, warn}; use std::sync::Arc; use tauri::path::BaseDirectory; use tauri::WindowEvent; @@ -18,6 +18,7 @@ mod app_identity; mod config_watcher; mod core; mod logging; +mod monitoring; mod output_log; mod plugin_bundles; mod plugin_manifest; @@ -36,6 +37,26 @@ mod utilities; mod validate; mod workarounds; +/// Loads `Client/.env` for local development without overwriting existing env vars. +/// +/// Missing .env file is silently ignored. Malformed or unreadable .env files +/// are reported with context for debugging before structured logging is ready. +fn load_local_dotenv() { + let dotenv_path = std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("../.env"); + + match dotenvy::from_path(&dotenv_path) { + Ok(_) => {} + Err(dotenvy::Error::Io(error)) if error.kind() == std::io::ErrorKind::NotFound => {} + Err(err) => { + eprintln!( + "warning: failed to load local .env from {}: {}", + dotenv_path.display(), + err + ); + } + } +} + /// Selects preferred backend from settings or first available plugin backend. /// /// # Parameters @@ -102,7 +123,13 @@ fn try_reopen_last_repo(app_handle: &tauri::AppHandle) { log::warn!("startup reopen: failed to emit repo:selected: {}", error); } } - Err(error) => log::warn!("startup reopen: failed to open repo: {}", error), + Err(error) => { + crate::monitoring::capture_startup_error( + "reopen_last_repo", + &error.to_string(), + ); + log::warn!("startup reopen: failed to open repo: {}", error) + } } } else { log::warn!("startup reopen: backend not available"); @@ -119,15 +146,18 @@ fn try_reopen_last_repo(app_handle: &tauri::AppHandle) { /// - `()`. This function runs the Tauri event loop until application exit. #[cfg_attr(mobile, tauri::mobile_entry_point)] pub fn run() { + load_local_dotenv(); // Initialize logging logging::init(); + let app_state = state::AppState::new_with_config(); + monitoring::sync_backend_monitoring(&app_state.config()); workarounds::apply_linux_nvidia_workaround(); println!("Running OpenVCS..."); tauri::Builder::default() - .manage(state::AppState::new_with_config()) + .manage(app_state) .setup(|app| { crate::plugin_runtime::host_api::set_status_event_emitter({ let app_handle = app.handle().clone(); @@ -191,16 +221,43 @@ pub fn run() { } let store = crate::plugin_bundles::PluginBundleStore::new_default(); if let Err(err) = store.sync_built_in_plugins() { + crate::monitoring::capture_startup_error("sync_built_in_plugins", &err.to_string()); warn!("plugins: failed to sync built-in plugins: {}", err); } let state = app.state::(); crate::config_watcher::start_config_watcher(app.handle().clone()); if let Err(err) = crate::plugin_sources::sync_configured_plugins(&state.config()) { + crate::monitoring::capture_startup_error( + "sync_configured_plugins", + &err.to_string(), + ); warn!("plugins: failed to sync configured plugins: {}", err); } if let Err(err) = state.plugin_runtime().sync_plugin_runtime() { + crate::monitoring::capture_startup_error("sync_plugin_runtime", &err.to_string()); warn!("plugins: failed to sync runtime on startup: {}", err); } + match crate::plugin_vcs_backends::list_plugin_vcs_backends() { + Ok(backends) if backends.is_empty() => { + let configured_default = state.config().general.default_backend; + error!( + "startup: no VCS backends are available; configured default backend='{}'; repo actions will remain unavailable until a backend plugin is installed, approved, and enabled", + configured_default + ); + } + Ok(backends) => { + log::info!( + "startup: {} VCS backend(s) available after plugin sync", + backends.len() + ); + } + Err(err) => { + warn!( + "startup: failed to discover VCS backends after plugin sync: {}", + err + ); + } + } // On startup, optionally reopen the last repository if enabled in settings. try_reopen_last_repo(app.handle()); @@ -344,6 +401,7 @@ fn build_invoke_handler( tauri_commands::log_frontend_message, tauri_commands::tail_app_log, tauri_commands::clear_app_log, + tauri_commands::report_frontend_error, tauri_commands::exit_app, tauri_commands::check_for_updates, ] diff --git a/Backend/src/logging.rs b/Backend/src/logging.rs index 853028d7..08f1eb23 100644 --- a/Backend/src/logging.rs +++ b/Backend/src/logging.rs @@ -1,14 +1,28 @@ // Copyright © 2025-2026 OpenVCS Contributors // SPDX-License-Identifier: GPL-3.0-or-later use crate::settings::{AppConfig, LogLevel}; +use sentry_log::{LogFilter, SentryLogger}; use std::fs::{self, OpenOptions}; use std::io::{Seek, SeekFrom, Write}; +use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::{Arc, Mutex, OnceLock}; use std::time::Instant; use time::{OffsetDateTime, UtcOffset}; use zip::{write::FileOptions, CompressionMethod, ZipWriter}; static ACTIVE_LOG_FILE: OnceLock>> = OnceLock::new(); +static SENTRY_LOG_FORWARDING_ENABLED: AtomicBool = AtomicBool::new(false); + +/// Updates whether backend log records should be forwarded into Sentry. +/// +/// # Parameters +/// - `enabled`: `true` when a backend Sentry client is active and crash reporting is allowed. +/// +/// # Returns +/// - `()`. +pub fn set_sentry_log_forwarding_enabled(enabled: bool) { + SENTRY_LOG_FORWARDING_ENABLED.store(enabled, Ordering::Relaxed); +} /// RAII timer that logs operation duration on drop. /// @@ -104,26 +118,6 @@ pub fn clear_active_log_file() -> Result<(), String> { Ok(()) } -/// Writes a line directly to the active log file. -/// -/// # Parameters -/// - `line`: The line to write (without trailing newline). -/// -/// # Returns -/// - `Ok(())` if the line was written. -/// - `Err(String)` if writing fails or log file not initialized. -pub fn write_to_log(line: &str) -> Result<(), String> { - let Some(file) = ACTIVE_LOG_FILE.get() else { - return Ok(()); - }; - let mut f = file - .lock() - .map_err(|_| "log file lock poisoned".to_string())?; - writeln!(f, "{}", line).map_err(|e| e.to_string())?; - f.flush().map_err(|e| e.to_string())?; - Ok(()) -} - /// Initialize logging: console (env_logger) + append to `./logs/openvcs.log`. /// Respects `RUST_LOG` for filtering; sets a sensible default if missing. /// @@ -363,15 +357,46 @@ pub fn init() { console: console_logger, file, }; - let _ = log::set_boxed_logger(Box::new(dual)); + let sentry_logger = build_sentry_logger(dual); + let _ = log::set_boxed_logger(Box::new(sentry_logger)); log::set_max_level(log::LevelFilter::Trace); } else { // Fallback to console-only - let _ = log::set_boxed_logger(Box::new(console_logger)); + let sentry_logger = build_sentry_logger(console_logger); + let _ = log::set_boxed_logger(Box::new(sentry_logger)); log::set_max_level(log::LevelFilter::Trace); } } +/// Wraps an existing backend logger so selected records are also forwarded to Sentry. +/// +/// Error-level records become Sentry events and logs, warn-level records become +/// breadcrumbs and logs, and info-level records become breadcrumbs. Local +/// console/file logging always continues through the wrapped destination logger. +/// +/// # Parameters +/// - `destination`: Existing logger that should continue receiving all records. +/// +/// # Returns +/// - A `SentryLogger` forwarding selected records to Sentry. +fn build_sentry_logger(destination: L) -> SentryLogger +where + L: log::Log + Send + Sync + 'static, +{ + SentryLogger::with_dest(destination).filter(|metadata| { + if !SENTRY_LOG_FORWARDING_ENABLED.load(Ordering::Relaxed) { + return LogFilter::Ignore; + } + + match metadata.level() { + log::Level::Error => LogFilter::Event | LogFilter::Log, + log::Level::Warn => LogFilter::Breadcrumb | LogFilter::Log, + log::Level::Info => LogFilter::Breadcrumb, + log::Level::Debug | log::Level::Trace => LogFilter::Ignore, + } + }) +} + /// Rotates existing active log into a timestamped zip archive. /// /// # Parameters diff --git a/Backend/src/monitoring.rs b/Backend/src/monitoring.rs new file mode 100644 index 00000000..ec156544 --- /dev/null +++ b/Backend/src/monitoring.rs @@ -0,0 +1,370 @@ +// Copyright © 2025-2026 OpenVCS Contributors +// SPDX-License-Identifier: GPL-3.0-or-later +//! Sentry-based crash reporting for the desktop backend. + +use std::borrow::Cow; +use std::sync::Mutex; + +use sentry::protocol::{Breadcrumb, Event, Exception, Frame, Map, Stacktrace, Value}; +use serde::Deserialize; + +use crate::settings::AppConfig; + +/// Environment variable containing the backend Sentry DSN. +const BACKEND_SENTRY_DSN_ENV: &str = "OPENVCS_SENTRY_DSN"; +/// Environment variable overriding the backend Sentry environment name. +const BACKEND_SENTRY_ENVIRONMENT_ENV: &str = "OPENVCS_SENTRY_ENVIRONMENT"; +/// Build-time fallback Sentry DSN embedded during CI/local builds. +const BACKEND_SENTRY_DSN_BUILT: Option<&str> = option_env!("OPENVCS_SENTRY_DSN_BUILT"); +/// Build-time fallback Sentry environment embedded during CI/local builds. +const BACKEND_SENTRY_ENVIRONMENT_BUILT: Option<&str> = + option_env!("OPENVCS_SENTRY_ENVIRONMENT_BUILT"); +/// Keeps the active backend Sentry guard alive for as long as reporting is enabled. +static BACKEND_MONITORING_GUARD: Mutex> = Mutex::new(None); + +/// Represents a relayed frontend breadcrumb forwarded to the backend. +#[derive(Debug, Clone, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct FrontendBreadcrumb { + /// Unix timestamp in milliseconds when the breadcrumb was recorded. + pub timestamp_ms: i64, + /// Severity level of the breadcrumb. + pub level: String, + /// Human-readable breadcrumb message. + pub message: String, +} + +/// Represents a normalized frontend error report captured in the webview. +#[derive(Debug, Clone, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct FrontendErrorReport { + /// Logical frontend error kind. + pub kind: String, + /// Primary error message. + pub message: String, + /// Optional JavaScript stack string. + pub stack: Option, + /// Optional script source URL or path. + pub source: Option, + /// Optional line number. + pub line: Option, + /// Optional column number. + pub column: Option, + /// Current window URL. + pub url: Option, + /// Current browser/webview user agent. + pub user_agent: Option, + /// Frontend release string from the build. + pub release: Option, + /// Frontend environment string from the build. + pub environment: Option, + /// Recent frontend breadcrumbs recorded before the error. + #[serde(default)] + pub breadcrumbs: Vec, +} + +/// Trims a string-like value and normalizes empty strings to `None`. +fn normalize_env_value(value: Option) -> Option { + value.and_then(|value| { + let normalized = value.trim().to_string(); + if normalized.is_empty() { + None + } else { + Some(normalized) + } + }) +} + +/// Returns whether backend crash reporting is allowed by current settings. +fn backend_monitoring_allowed(cfg: &AppConfig) -> bool { + cfg.general.crash_reports +} + +/// Builds a backend Sentry guard when configuration and environment permit it. +fn build_backend_monitoring_guard(cfg: &AppConfig) -> Option { + if !backend_monitoring_allowed(cfg) { + log::info!("monitoring: backend Sentry disabled by configuration"); + return None; + } + + let dsn = normalize_env_value(std::env::var(BACKEND_SENTRY_DSN_ENV).ok()) + .or_else(|| normalize_env_value(BACKEND_SENTRY_DSN_BUILT.map(str::to_string))); + let Some(dsn) = dsn else { + log::info!( + "monitoring: backend Sentry disabled because {BACKEND_SENTRY_DSN_ENV} is unset and no build-time fallback was embedded" + ); + return None; + }; + + let environment = normalize_env_value(std::env::var(BACKEND_SENTRY_ENVIRONMENT_ENV).ok()) + .or_else(|| normalize_env_value(BACKEND_SENTRY_ENVIRONMENT_BUILT.map(str::to_string))) + .unwrap_or_else(|| "desktop".to_string()); + let release = format!("openvcs-client@{}", env!("OPENVCS_VERSION")); + + let guard = sentry::init(( + dsn, + sentry::ClientOptions { + release: Some(Cow::Owned(release)), + environment: Some(Cow::Owned(environment)), + attach_stacktrace: true, + enable_logs: true, + ..Default::default() + }, + )); + + sentry::configure_scope(|scope| { + scope.set_tag("component", "backend"); + scope.set_tag("channel", env!("OPENVCS_APP_CHANNEL")); + scope.set_tag("version", env!("OPENVCS_VERSION")); + }); + + log::info!("monitoring: backend Sentry enabled"); + Some(guard) +} + +/// Synchronizes backend Sentry reporting with the latest persisted configuration. +/// +/// # Parameters +/// - `cfg`: Current application configuration used for crash-report consent. +/// +/// # Returns +/// - `()`. +pub fn sync_backend_monitoring(cfg: &AppConfig) { + let mut active_guard = match BACKEND_MONITORING_GUARD.lock() { + Ok(guard) => guard, + Err(poisoned) => { + log::warn!("monitoring: recovering from poisoned backend monitoring mutex"); + poisoned.into_inner() + } + }; + *active_guard = build_backend_monitoring_guard(cfg); + crate::logging::set_sentry_log_forwarding_enabled(active_guard.is_some()); +} + +/// Captures a startup-time backend error with operation-specific tags. +/// +/// # Parameters +/// - `operation`: Short name of the failing startup step. +/// - `error`: Error description to attach to the captured event. +/// +/// # Returns +/// - `()`. +pub fn capture_startup_error(operation: &str, error: &str) { + if sentry::Hub::current().client().is_none() { + return; + } + + sentry::with_scope( + |scope| { + scope.set_tag("component", "backend"); + scope.set_tag("phase", "startup"); + scope.set_tag("operation", operation.to_string()); + }, + || { + sentry::capture_message( + &format!("startup {operation} failed: {error}"), + sentry::Level::Error, + ); + }, + ); +} + +/// Captures a normalized frontend error through the backend-owned Sentry client. +/// +/// # Parameters +/// - `payload`: Normalized frontend error report from the webview. +/// +/// # Returns +/// - `()`. +pub fn capture_frontend_error(payload: FrontendErrorReport) { + if sentry::Hub::current().client().is_none() { + return; + } + + let level = sentry::Level::Error; + let stack = payload.stack.clone(); + let message = payload.message.clone(); + let breadcrumbs = payload + .breadcrumbs + .iter() + .map(build_frontend_breadcrumb) + .collect::>(); + + let mut extra = Map::new(); + insert_extra(&mut extra, "source", payload.source.clone()); + insert_extra(&mut extra, "url", payload.url.clone()); + insert_extra(&mut extra, "user_agent", payload.user_agent.clone()); + insert_extra(&mut extra, "release", payload.release.clone()); + insert_extra(&mut extra, "environment", payload.environment.clone()); + insert_extra(&mut extra, "stack", stack); + if let Some(line) = payload.line { + extra.insert("line".into(), Value::from(line)); + } + if let Some(column) = payload.column { + extra.insert("column".into(), Value::from(column)); + } + + let event = Event { + level, + message: Some(message), + logger: Some("frontend".into()), + release: payload.release.map(Cow::Owned), + environment: payload.environment.map(Cow::Owned), + exception: vec![Exception { + ty: payload.kind.clone(), + value: Some(payload.message), + stacktrace: parse_frontend_stacktrace(payload.stack.as_deref()), + ..Default::default() + }] + .into(), + breadcrumbs: breadcrumbs.into(), + extra, + ..Default::default() + }; + + sentry::with_scope( + |scope| { + scope.set_tag("component", "frontend"); + scope.set_tag("runtime", "tauri-webview"); + scope.set_tag("frontend_kind", payload.kind.clone()); + }, + || { + sentry::capture_event(event); + }, + ); +} + +/// Parses a JavaScript stack string into a Sentry stacktrace when possible. +fn parse_frontend_stacktrace(stack: Option<&str>) -> Option { + let stack = stack?.trim(); + if stack.is_empty() { + return None; + } + + let mut frames = stack + .lines() + .filter_map(parse_frontend_stack_frame) + .collect::>(); + if frames.is_empty() { + return None; + } + + frames.reverse(); + Some(Stacktrace { + frames, + ..Default::default() + }) +} + +/// Parses a single JavaScript stack frame line into a Sentry frame. +fn parse_frontend_stack_frame(line: &str) -> Option { + let trimmed = line.trim(); + if trimmed.is_empty() { + return None; + } + + let without_at = trimmed.strip_prefix("at ").unwrap_or(trimmed); + let (function, location) = if let Some(open_paren) = without_at.rfind(" (") { + let function = without_at[..open_paren].trim(); + let location = without_at + .get(open_paren + 2..without_at.len().saturating_sub(1))? + .trim(); + (normalize_stack_part(function), location) + } else if let Some((function, location)) = without_at.rsplit_once('@') { + (normalize_stack_part(function), location.trim()) + } else { + (None, without_at) + }; + + let (filename, lineno, colno) = parse_frontend_location(location)?; + Some(Frame { + function, + filename: Some(filename), + lineno, + colno, + ..Default::default() + }) +} + +/// Normalizes an optional function name from a JavaScript stack frame. +fn normalize_stack_part(value: &str) -> Option { + let value = value.trim(); + (!value.is_empty()).then(|| value.to_string()) +} + +/// Parses a `url:line:column` location suffix from a JavaScript stack frame. +fn parse_frontend_location(location: &str) -> Option<(String, Option, Option)> { + let (prefix, colno_raw) = location.rsplit_once(':')?; + let (filename_raw, lineno_raw) = prefix.rsplit_once(':')?; + let filename = filename_raw.trim().to_string(); + if filename.is_empty() { + return None; + } + + let lineno = lineno_raw.trim().parse::().ok(); + let colno = colno_raw.trim().parse::().ok(); + Some((filename, lineno, colno)) +} + +/// Builds a Sentry breadcrumb from a relayed frontend breadcrumb payload. +fn build_frontend_breadcrumb(breadcrumb: &FrontendBreadcrumb) -> Breadcrumb { + Breadcrumb { + ty: "default".into(), + category: Some("console".into()), + level: parse_frontend_level(&breadcrumb.level), + message: Some(breadcrumb.message.clone()), + timestamp: std::time::UNIX_EPOCH + + std::time::Duration::from_millis(breadcrumb.timestamp_ms.max(0) as u64), + ..Default::default() + } +} + +/// Maps relayed frontend levels to Sentry levels. +fn parse_frontend_level(level: &str) -> sentry::Level { + match level { + "fatal" => sentry::Level::Fatal, + "error" => sentry::Level::Error, + "warning" | "warn" => sentry::Level::Warning, + "info" => sentry::Level::Info, + _ => sentry::Level::Debug, + } +} + +/// Inserts an optional string into a Sentry event extra map. +fn insert_extra(extra: &mut Map, key: &str, value: Option) { + if let Some(value) = value { + extra.insert(key.into(), Value::String(value)); + } +} + +#[cfg(test)] +mod tests { + use super::{parse_frontend_level, parse_frontend_location, parse_frontend_stacktrace}; + + #[test] + fn parses_frontend_location_suffix() { + let parsed = parse_frontend_location("app://index.js:12:34"); + assert_eq!(parsed, Some(("app://index.js".into(), Some(12), Some(34)))); + } + + #[test] + fn parses_frontend_stacktrace_frames() { + let stacktrace = parse_frontend_stacktrace(Some( + "Error: boom\n at alpha (app://index.js:2:3)\n at beta (app://index.js:4:5)", + )) + .expect("stacktrace expected"); + + assert_eq!(stacktrace.frames.len(), 2); + assert_eq!(stacktrace.frames[0].function.as_deref(), Some("beta")); + assert_eq!(stacktrace.frames[0].lineno, Some(4)); + assert_eq!(stacktrace.frames[1].function.as_deref(), Some("alpha")); + assert_eq!(stacktrace.frames[1].colno, Some(3)); + } + + #[test] + fn maps_frontend_levels_to_sentry_levels() { + assert_eq!(parse_frontend_level("warning"), sentry::Level::Warning); + assert_eq!(parse_frontend_level("error"), sentry::Level::Error); + assert_eq!(parse_frontend_level("other"), sentry::Level::Debug); + } +} diff --git a/Backend/src/plugin_runtime/node_instance.rs b/Backend/src/plugin_runtime/node_instance.rs index a5dca86e..60aa8153 100644 --- a/Backend/src/plugin_runtime/node_instance.rs +++ b/Backend/src/plugin_runtime/node_instance.rs @@ -732,6 +732,12 @@ impl NodePluginRuntimeInstance { self.rpc_call_unit(Methods::VCS_STAGE_PATCH, params) } + /// Calls `vcs.stage-paths`. + pub fn vcs_stage_paths(&self, paths: &[String]) -> Result<(), String> { + let params = self.session_params(json!({ "paths": paths }))?; + self.rpc_call_unit(Methods::VCS_STAGE_PATHS, params) + } + /// Calls `vcs.discard-paths`. pub fn vcs_discard_paths(&self, paths: &[String]) -> Result<(), String> { let params = self.session_params(json!({ "paths": paths }))?; diff --git a/Backend/src/plugin_runtime/protocol.rs b/Backend/src/plugin_runtime/protocol.rs index 3762eb07..9f1ce869 100644 --- a/Backend/src/plugin_runtime/protocol.rs +++ b/Backend/src/plugin_runtime/protocol.rs @@ -94,6 +94,8 @@ impl Methods { pub const VCS_WRITE_MERGE_RESULT: &'static str = "vcs.write_merge_result"; /// Stages a text patch. pub const VCS_STAGE_PATCH: &'static str = "vcs.stage_patch"; + /// Stages explicit paths to the index. + pub const VCS_STAGE_PATHS: &'static str = "vcs.stage_paths"; /// Discards path changes. pub const VCS_DISCARD_PATHS: &'static str = "vcs.discard_paths"; /// Applies reverse patch. diff --git a/Backend/src/plugin_runtime/vcs_proxy.rs b/Backend/src/plugin_runtime/vcs_proxy.rs index bce620fc..b1750818 100644 --- a/Backend/src/plugin_runtime/vcs_proxy.rs +++ b/Backend/src/plugin_runtime/vcs_proxy.rs @@ -241,6 +241,16 @@ impl Vcs for PluginVcsProxy { .map_err(|e| self.map_runtime_error(e)) } + fn stage_paths(&self, paths: &[PathBuf]) -> VcsResult<()> { + let paths = paths + .iter() + .map(|p| p.to_string_lossy().to_string()) + .collect::>(); + self.runtime + .vcs_stage_paths(&paths) + .map_err(|e| self.map_runtime_error(e)) + } + fn discard_paths(&self, paths: &[PathBuf]) -> VcsResult<()> { let paths = paths .iter() diff --git a/Backend/src/plugins.rs b/Backend/src/plugins.rs index a3dc660f..5136f70b 100644 --- a/Backend/src/plugins.rs +++ b/Backend/src/plugins.rs @@ -281,7 +281,7 @@ impl PluginCache { } } - summaries.sort_by(|a, b| a.name.to_lowercase().cmp(&b.name.to_lowercase())); + summaries.sort_by_key(|a| a.name.to_lowercase()); let mut data = self.data.write().unwrap(); data.list = summaries; data.entries = entries; diff --git a/Backend/src/settings.rs b/Backend/src/settings.rs index 69be2d4c..2df7739d 100644 --- a/Backend/src/settings.rs +++ b/Backend/src/settings.rs @@ -85,13 +85,13 @@ pub struct General { pub default_backend: String, #[serde(default)] pub update_channel: UpdateChannel, - #[serde(default)] + #[serde(default = "default_true")] pub reopen_last_repos: bool, - #[serde(default)] + #[serde(default = "default_true")] pub checks_on_launch: bool, #[serde(default)] pub telemetry: bool, - #[serde(default)] + #[serde(default = "default_true")] pub crash_reports: bool, } impl Default for General { @@ -109,7 +109,7 @@ impl Default for General { reopen_last_repos: true, checks_on_launch: true, telemetry: false, - crash_reports: false, + crash_reports: true, } } } diff --git a/Backend/src/state.rs b/Backend/src/state.rs index 46452e94..1755152f 100644 --- a/Backend/src/state.rs +++ b/Backend/src/state.rs @@ -126,6 +126,7 @@ impl AppState { next.validate(); next.save().map_err(|e| e.to_string())?; apply_git_ssh_env(&next); + crate::monitoring::sync_backend_monitoring(&next); *self.config.write() = next; self.enforce_recents_limit_and_persist(); Ok(()) @@ -311,10 +312,8 @@ fn load_recents_from_disk() -> Result, String> { if let Ok(serde_json::Value::Array(items)) = serde_json::from_str::(&data) { for it in items { match it { - serde_json::Value::String(s) => { - if !s.trim().is_empty() { - out.push(PathBuf::from(s)); - } + serde_json::Value::String(s) if !s.trim().is_empty() => { + out.push(PathBuf::from(s)); } serde_json::Value::Object(map) => { if let Some(serde_json::Value::String(s)) = map.get("path") { diff --git a/Backend/src/tauri_commands/commit.rs b/Backend/src/tauri_commands/commit.rs index 466672dd..93624399 100644 --- a/Backend/src/tauri_commands/commit.rs +++ b/Backend/src/tauri_commands/commit.rs @@ -125,10 +125,6 @@ pub async fn commit_selected( async_runtime::spawn_blocking(move || { let on = progress_bridge(app); - on(VcsEvent::Info { - msg: "Staging selected files…".into(), - }); - let (name, email) = repo .inner() .get_identity() @@ -146,6 +142,14 @@ pub async fn commit_selected( let paths: Vec = files.into_iter().map(PathBuf::from).collect(); + on(VcsEvent::Info { + msg: "Staging selected files…".into(), + }); + repo.inner().stage_paths(&paths).map_err(|e| { + error!("stage_paths failed: {e}"); + e.to_string() + })?; + on(VcsEvent::Info { msg: "Writing commit…".into(), }); @@ -312,7 +316,14 @@ pub async fn commit_patch_and_files( .commit_index(&message, &name, &email) .map_err(|e| e.to_string())? } else { - let paths: Vec = files.into_iter().map(PathBuf::from).collect(); + let paths: Vec = files.iter().map(PathBuf::from).collect(); + on(VcsEvent::Info { + msg: "Staging selected files…".into(), + }); + repo.inner().stage_paths(&paths).map_err(|e| { + error!("stage_paths failed: {e}"); + e.to_string() + })?; repo.inner() .commit(&message, &name, &email, &paths) .map_err(|e| e.to_string())? diff --git a/Backend/src/tauri_commands/mod.rs b/Backend/src/tauri_commands/mod.rs index 47f2f3a9..2d61d627 100644 --- a/Backend/src/tauri_commands/mod.rs +++ b/Backend/src/tauri_commands/mod.rs @@ -10,6 +10,7 @@ mod branches; mod commit; mod conflicts; mod general; +mod monitoring; mod output_log; mod plugins; mod remotes; @@ -27,6 +28,7 @@ pub use branches::*; pub use commit::*; pub use conflicts::*; pub use general::*; +pub use monitoring::*; pub use output_log::*; pub use plugins::*; pub use remotes::*; diff --git a/Backend/src/tauri_commands/monitoring.rs b/Backend/src/tauri_commands/monitoring.rs new file mode 100644 index 00000000..a7371dff --- /dev/null +++ b/Backend/src/tauri_commands/monitoring.rs @@ -0,0 +1,17 @@ +// Copyright © 2025-2026 OpenVCS Contributors +// SPDX-License-Identifier: GPL-3.0-or-later + +use crate::monitoring::FrontendErrorReport; + +#[tauri::command] +/// Reports a frontend error payload to the backend-owned monitoring pipeline. +/// +/// # Parameters +/// - `payload`: Normalized frontend error report from the Tauri webview. +/// +/// # Returns +/// - `Ok(())` after the payload has been offered to the monitoring backend. +pub fn report_frontend_error(payload: FrontendErrorReport) -> Result<(), String> { + crate::monitoring::capture_frontend_error(payload); + Ok(()) +} diff --git a/Backend/src/tauri_commands/output_log.rs b/Backend/src/tauri_commands/output_log.rs index d04cebb8..196c566c 100644 --- a/Backend/src/tauri_commands/output_log.rs +++ b/Backend/src/tauri_commands/output_log.rs @@ -97,20 +97,13 @@ pub fn log_frontend_message(state: tauri::State<'_, AppState>, level: String, me _ => (OutputLevel::Info, log::Level::Info), }; - // Write directly to stderr with [FRONTEND] tag - let now = time::OffsetDateTime::now_utc(); - let timestamp = format!( - "{:04}-{:02}-{:02} {:02}:{:02}:{:02}", - now.year(), - now.month() as u8, - now.day(), - now.hour(), - now.minute(), - now.second() - ); - let log_line = format!("{} {:5} [FRONTEND]: {}", timestamp, log_level, message); - eprintln!("{}", log_line); - let _ = crate::logging::write_to_log(&log_line); + let args = format_args!("{}", message); + let record = log::Record::builder() + .args(args) + .level(log_level) + .target("frontend") + .build(); + log::logger().log(&record); let entry = OutputLogEntry::new( std::time::SystemTime::now() diff --git a/Backend/src/tauri_commands/plugins.rs b/Backend/src/tauri_commands/plugins.rs index eb682ad7..0cc3bc73 100644 --- a/Backend/src/tauri_commands/plugins.rs +++ b/Backend/src/tauri_commands/plugins.rs @@ -1,7 +1,7 @@ // Copyright © 2025-2026 OpenVCS Contributors // SPDX-License-Identifier: GPL-3.0-or-later use crate::core::settings::{SettingKv, SettingValue}; -use crate::core::ui::{Menu, UiElement}; +use crate::core::ui::{Menu, MenuSurface, UiElement}; use crate::plugin_bundles::{InstalledPluginIndex, PluginBundleStore}; use crate::plugin_runtime::instance::PluginRuntimeInstance; use crate::plugin_runtime::settings_store; @@ -63,6 +63,8 @@ pub struct PluginMenuPayload { pub id: String, /// User-visible label. pub label: String, + /// Render target surface. + pub surface: MenuSurface, /// Renderable menu elements. pub elements: Vec, } @@ -420,6 +422,7 @@ fn menu_to_payload(plugin_id: &str, menu: Menu) -> PluginMenuPayload { plugin_id: plugin_id.to_string(), id: menu.id, label: menu.label, + surface: menu.surface, elements, } } diff --git a/Backend/src/tauri_commands/repo_files.rs b/Backend/src/tauri_commands/repo_files.rs index 4aa4cbd2..7db42002 100644 --- a/Backend/src/tauri_commands/repo_files.rs +++ b/Backend/src/tauri_commands/repo_files.rs @@ -197,7 +197,7 @@ fn decode_repo_text(bytes: &[u8]) -> String { i += 2; } let mut out = String::new(); - for ch in std::char::decode_utf16(u16s.into_iter()) { + for ch in std::char::decode_utf16(u16s) { match ch { Ok(c) => out.push(c), Err(_) => return String::from_utf8_lossy(bytes).to_string(), diff --git a/Backend/src/tauri_commands/shared.rs b/Backend/src/tauri_commands/shared.rs index 3f4f3e36..b8570386 100644 --- a/Backend/src/tauri_commands/shared.rs +++ b/Backend/src/tauri_commands/shared.rs @@ -63,9 +63,15 @@ pub(crate) fn progress_bridge(app: AppHandle) -> OnEvent { /// - `Ok(Arc)` for the active repository. /// - `Err(String)` when no repo is selected or backend is unavailable. pub(crate) fn current_repo_or_err(state: &State<'_, AppState>) -> Result, String> { - let repo = state - .current_repo() - .ok_or_else(|| "No repository selected".to_string())?; + let repo = match state.current_repo() { + Some(repo) => repo, + None => { + log::warn!( + "repo command aborted: no repository selected; possible causes include no VCS backend available, reopen/startup failure, or the active repo being cleared" + ); + return Err("No repository selected".to_string()); + } + }; let backend_id = repo.id(); let is_available = plugin_vcs_backends::has_plugin_vcs_backend(&backend_id); @@ -73,6 +79,10 @@ pub(crate) fn current_repo_or_err(state: &State<'_, AppState>) -> Result Vec { } } - summaries.sort_by(|a, b| a.name.to_lowercase().cmp(&b.name.to_lowercase())); + summaries.sort_by_key(|a| a.name.to_lowercase()); let mut out = Vec::with_capacity(summaries.len() + 1); out.push(default_theme_summary()); diff --git a/Backend/src/validate.rs b/Backend/src/validate.rs index 32863a20..e49e2938 100644 --- a/Backend/src/validate.rs +++ b/Backend/src/validate.rs @@ -1,6 +1,15 @@ // Copyright © 2025-2026 OpenVCS Contributors // SPDX-License-Identifier: GPL-3.0-or-later use std::path::Path; +use std::sync::LazyLock; + +/// Regex pattern for scp-like Git URLs. +static SCP_LIKE_RE: LazyLock = + LazyLock::new(|| regex::Regex::new(r"^[\w.-]+@[\w.-]+:[\w./-]+\.git$").unwrap()); + +/// Regex pattern for Windows absolute paths. +static WIN_ABS_RE: LazyLock = + LazyLock::new(|| regex::Regex::new(r"^[A-Za-z]:[\\/]").unwrap()); #[derive(serde::Serialize)] pub struct Validation { @@ -51,8 +60,7 @@ fn is_probably_git_url(u: &str) -> bool { return true; } // scp-like: git@host:org/repo.git - let scp_like = regex::Regex::new(r"^[\w.-]+@[\w.-]+:[\w./-]+\.git$").unwrap(); - if scp_like.is_match(u) { + if SCP_LIKE_RE.is_match(u) { return true; } false @@ -76,8 +84,7 @@ fn looks_like_path(s: &str) -> bool { return true; } // Windows drive letter absolute, e.g. C:\... - let win_abs = regex::Regex::new(r"^[A-Za-z]:[\\/]").unwrap(); - win_abs.is_match(s) + WIN_ABS_RE.is_match(s) } /// Validates whether a string looks like a supported Git URL. diff --git a/Cargo.lock b/Cargo.lock index d63ec839..d142a79c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,15 @@ # It is not intended for manual editing. version = 4 +[[package]] +name = "addr2line" +version = "0.25.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b5d307320b3181d6d7954e663bd7c774a838b8220fe0593c86d9fb09f498b4b" +dependencies = [ + "gimli", +] + [[package]] name = "adler2" version = "2.0.1" @@ -215,9 +224,9 @@ dependencies = [ [[package]] name = "async-signal" -version = "0.2.13" +version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43c070bbf59cd3570b6b2dd54cd772527c7c3620fce8be898406dd3ed6adc64c" +checksum = "52b5aaafa020cf5053a01f2a60e8ff5dccf550f0f77ec54a4e47285ac2bab485" dependencies = [ "async-io", "async-lock", @@ -283,6 +292,21 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" +[[package]] +name = "backtrace" +version = "0.3.76" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb531853791a215d7c62a30daf0dde835f381ab5de4589cfe7c649d2cbe92bd6" +dependencies = [ + "addr2line", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", + "windows-link 0.2.1", +] + [[package]] name = "base64" version = "0.21.7" @@ -318,9 +342,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.11.0" +version = "2.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af" +checksum = "c4512299f36f043ab09a583e57bceb5a5aab7a73db1805848e8fef3c9e8c78b3" dependencies = [ "serde_core", ] @@ -428,7 +452,7 @@ version = "0.18.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ca26ef0159422fb77631dc9d17b102f253b876fe1586b03b803e63a309b4ee2" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "cairo-sys-rs", "glib", "libc", @@ -491,9 +515,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.57" +version = "1.2.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a0dd1ca384932ff3641c8718a02769f1698e7563dc6974ffd03346116310423" +checksum = "43c5703da9466b66a946814e1adf53ea2c90f10063b86290cc9eb67ce3478a20" dependencies = [ "find-msvc-tools", "jobserver", @@ -534,6 +558,12 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" +[[package]] +name = "cfg_aliases" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" + [[package]] name = "chrono" version = "0.4.44" @@ -631,7 +661,7 @@ version = "0.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "064badf302c3194842cf2c5d61f56cc88e54a759313879cdf03abdd27d0c3b97" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "core-foundation", "core-graphics-types", "foreign-types", @@ -644,7 +674,7 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d44a101f213f6c4cdc1853d4b78aef6db6bdfa3468798cc1d9912f4735013eb" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "core-foundation", "libc", ] @@ -794,6 +824,16 @@ dependencies = [ "syn 2.0.117", ] +[[package]] +name = "debugid" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef552e6f588e446098f6ba40d89ac146c8c7b64aade83c051ee00bb5d2bc18d" +dependencies = [ + "serde", + "uuid", +] + [[package]] name = "deflate64" version = "0.1.12" @@ -913,7 +953,7 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e0e367e4e7da84520dedcac1901e4da967309406d1e51017ae1abfb97adbd38" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "block2", "libc", "objc2", @@ -968,6 +1008,12 @@ dependencies = [ "tendril 0.5.0", ] +[[package]] +name = "dotenvy" +version = "0.15.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b" + [[package]] name = "dpi" version = "0.1.2" @@ -1124,9 +1170,9 @@ dependencies = [ [[package]] name = "fastrand" -version = "2.3.0" +version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" +checksum = "9f1f227452a390804cdb637b74a86990f2a7d7ba4b7d5693aac9b4dd6defd8d6" [[package]] name = "fdeflate" @@ -1255,6 +1301,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d" dependencies = [ "futures-core", + "futures-sink", ] [[package]] @@ -1468,8 +1515,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" dependencies = [ "cfg-if", + "js-sys", "libc", "wasi 0.11.1+wasi-snapshot-preview1", + "wasm-bindgen", ] [[package]] @@ -1479,9 +1528,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" dependencies = [ "cfg-if", + "js-sys", "libc", "r-efi 5.3.0", "wasip2", + "wasm-bindgen", ] [[package]] @@ -1499,6 +1550,12 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "gimli" +version = "0.32.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e629b9b98ef3dd8afe6ca2bd0f89306cec16d43d907889945bc5d6687f2f13c7" + [[package]] name = "gio" version = "0.18.4" @@ -1537,7 +1594,7 @@ version = "0.18.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "233daaf6e83ae6a12a52055f568f9d7cf4671dabb78ff9560ab6da230ce00ee5" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "futures-channel", "futures-core", "futures-executor", @@ -1664,9 +1721,9 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.16.1" +version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" +checksum = "4f467dd6dccf739c208452f8014c75c18bb8301b050ad1cfb27153803edb0f51" [[package]] name = "heck" @@ -1701,6 +1758,17 @@ dependencies = [ "digest 0.10.7", ] +[[package]] +name = "hostname" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "617aaa3557aef3810a6369d0a99fac8a080891b68bd9f9812a1eeda0c0730cbd" +dependencies = [ + "cfg-if", + "libc", + "windows-link 0.2.1", +] + [[package]] name = "html5ever" version = "0.29.1" @@ -1762,20 +1830,26 @@ version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" +[[package]] +name = "httpdate" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" + [[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", @@ -1786,7 +1860,6 @@ dependencies = [ "httparse", "itoa", "pin-project-lite", - "pin-utils", "smallvec", "tokio", "want", @@ -1794,18 +1867,18 @@ dependencies = [ [[package]] name = "hyper-rustls" -version = "0.27.7" +version = "0.27.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3c93eb611681b207e1fe55d5a71ecf91572ec8a6705cdb6857f7d8d5242cf58" +checksum = "33ca68d021ef39cf6463ab54c1d0f5daf03377b70561305bb89a8f83aab66e0f" dependencies = [ "http", "hyper", "hyper-util", "rustls", - "rustls-pki-types", "tokio", "tokio-rustls", "tower-service", + "webpki-roots", ] [[package]] @@ -1867,12 +1940,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", @@ -1880,9 +1954,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", @@ -1893,9 +1967,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", @@ -1907,15 +1981,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", @@ -1927,15 +2001,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", @@ -1992,12 +2066,12 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.13.0" +version = "2.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017" +checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" dependencies = [ "equivalent", - "hashbrown 0.16.1", + "hashbrown 0.17.0", "serde", "serde_core", ] @@ -2017,7 +2091,7 @@ version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bd5b3eaf1a28b758ac0faa5a4254e8ab2705605496f1b1f3fbbc3988ad73d199" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "inotify-sys", "libc", ] @@ -2048,9 +2122,9 @@ checksum = "d98f6fed1fde3f8c21bc40a1abb88dd75e67924f9cffc3ef95607bad8017f8e2" [[package]] name = "iri-string" -version = "0.7.11" +version = "0.7.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8e7418f59cc01c88316161279a7f665217ae316b388e58a0d10e29f54f1e5eb" +checksum = "25e659a4bb38e810ebc252e53b5814ff908a8c58c2a9ce2fae1bbec24cbf4e20" dependencies = [ "memchr", "serde", @@ -2190,10 +2264,12 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.91" +version = "0.3.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b49715b7073f385ba4bc528e5747d02e66cb39c6146efb66b781f131f0fb399c" +checksum = "2964e92d1d9dc3364cae4d718d93f227e3abb088e747d92e0395bfdedf1c12ca" dependencies = [ + "cfg-if", + "futures-util", "once_cell", "wasm-bindgen", ] @@ -2226,7 +2302,7 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b750dcadc39a09dbadd74e118f6dd6598df77fa01df0cfcdc52c28dece74528a" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "serde", "unicode-segmentation", ] @@ -2259,7 +2335,7 @@ checksum = "02cb977175687f33fa4afa0c95c112b987ea1443e5a51c8f8ff27dc618270cc2" dependencies = [ "cssparser 0.29.6", "html5ever 0.29.1", - "indexmap 2.13.0", + "indexmap 2.14.0", "selectors 0.24.0", ] @@ -2295,15 +2371,15 @@ dependencies = [ [[package]] name = "libbz2-rs-sys" -version = "0.2.2" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c4a545a15244c7d945065b5d392b2d2d7f21526fba56ce51467b06ed445e8f7" +checksum = "b3a6a8c165077efc8f3a971534c50ea6a1a18b329ef4a66e897a7e3a1494565f" [[package]] name = "libc" -version = "0.2.183" +version = "0.2.185" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5b646652bf6661599e1da8901b3b9522896f01e736bad5f723fe7a3a27f899d" +checksum = "52ff2c0fe9bc6cb6b14a0592c2ff4fa9ceb83eea9db979b0487cd054946a2b8f" [[package]] name = "libloading" @@ -2317,14 +2393,14 @@ dependencies = [ [[package]] name = "libredox" -version = "0.1.15" +version = "0.1.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ddbf48fd451246b1f8c2610bd3b4ac0cc6e149d89832867093ab69a17194f08" +checksum = "e02f3bb43d335493c96bf3fd3a321600bf6bd07ed34bc64118e9293bdffea46c" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "libc", "plain", - "redox_syscall 0.7.3", + "redox_syscall 0.7.4", ] [[package]] @@ -2335,9 +2411,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 = "lock_api" @@ -2354,6 +2430,12 @@ version = "0.4.29" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" +[[package]] +name = "lru-slab" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" + [[package]] name = "lzma-rust2" version = "0.16.2" @@ -2462,9 +2544,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", @@ -2487,7 +2569,7 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3f42e7bbe13d351b6bead8286a43aac9534b82bd3cc43e47037f012ebfd62d4" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "jni-sys 0.3.1", "log", "ndk-sys", @@ -2517,6 +2599,18 @@ version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" +[[package]] +name = "nix" +version = "0.30.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6" +dependencies = [ + "bitflags 2.11.1", + "cfg-if", + "cfg_aliases", + "libc", +] + [[package]] name = "nodrop" version = "0.1.14" @@ -2529,7 +2623,7 @@ version = "8.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4d3d07927151ff8575b7087f245456e549fea62edf0ec4e565a5ee50c8402bc3" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "fsevent-sys", "inotify", "kqueue", @@ -2547,14 +2641,14 @@ version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42b8cfee0e339a0337359f3c88165702ac6e600dc01c0cc9579a92d62b08477a" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", ] [[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" @@ -2612,20 +2706,41 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d49e936b501e5c5bf01fda3a9452ff86dc3ea98ad5f283e1455153142d97518c" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "block2", "objc2", "objc2-core-foundation", "objc2-foundation", ] +[[package]] +name = "objc2-cloud-kit" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73ad74d880bb43877038da939b7427bba67e9dd42004a18b809ba7d87cee241c" +dependencies = [ + "bitflags 2.11.1", + "objc2", + "objc2-foundation", +] + +[[package]] +name = "objc2-core-data" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b402a653efbb5e82ce4df10683b6b28027616a2715e90009947d50b8dd298fa" +dependencies = [ + "objc2", + "objc2-foundation", +] + [[package]] name = "objc2-core-foundation" version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "dispatch2", "objc2", ] @@ -2636,13 +2751,45 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e022c9d066895efa1345f8e33e584b9f958da2fd4cd116792e15e07e4720a807" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "dispatch2", "objc2", "objc2-core-foundation", "objc2-io-surface", ] +[[package]] +name = "objc2-core-image" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5d563b38d2b97209f8e861173de434bd0214cf020e3423a52624cd1d989f006" +dependencies = [ + "objc2", + "objc2-foundation", +] + +[[package]] +name = "objc2-core-location" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca347214e24bc973fc025fd0d36ebb179ff30536ed1f80252706db19ee452009" +dependencies = [ + "objc2", + "objc2-foundation", +] + +[[package]] +name = "objc2-core-text" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cde0dfb48d25d2b4862161a4d5fcc0e3c24367869ad306b0c9ec0073bfed92d" +dependencies = [ + "bitflags 2.11.1", + "objc2", + "objc2-core-foundation", + "objc2-core-graphics", +] + [[package]] name = "objc2-encode" version = "4.1.0" @@ -2664,7 +2811,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3e0adef53c21f888deb4fa59fc59f7eb17404926ee8a6f59f5df0fd7f9f3272" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "block2", "libc", "objc2", @@ -2677,7 +2824,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "180788110936d59bab6bd83b6060ffdfffb3b922ba1396b312ae795e1de9d81d" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "objc2", "objc2-core-foundation", ] @@ -2688,7 +2835,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f112d1746737b0da274ef79a23aac283376f335f4095a083a267a082f21db0c0" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "objc2", "objc2-app-kit", "objc2-foundation", @@ -2700,7 +2847,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "96c1358452b371bf9f104e21ec536d37a650eb10f7ee379fff67d2e08d537f1f" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "objc2", "objc2-core-foundation", "objc2-foundation", @@ -2712,9 +2859,28 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d87d638e33c06f577498cbcc50491496a3ed4246998a7fbba7ccb98b1e7eab22" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", + "block2", "objc2", + "objc2-cloud-kit", + "objc2-core-data", "objc2-core-foundation", + "objc2-core-graphics", + "objc2-core-image", + "objc2-core-location", + "objc2-core-text", + "objc2-foundation", + "objc2-quartz-core", + "objc2-user-notifications", +] + +[[package]] +name = "objc2-user-notifications" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9df9128cbbfef73cda168416ccf7f837b62737d748333bfe9ab71c245d76613e" +dependencies = [ + "objc2", "objc2-foundation", ] @@ -2724,7 +2890,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b2e5aaab980c433cf470df9d7af96a7b46a9d892d521a2cbbb2f8a4c16751e7f" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "block2", "objc2", "objc2-app-kit", @@ -2732,6 +2898,15 @@ dependencies = [ "objc2-foundation", ] +[[package]] +name = "object" +version = "0.37.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff76201f031d8863c38aa7f905eca4f53abbfa15f609db4277d44cd8938f33fe" +dependencies = [ + "memchr", +] + [[package]] name = "once_cell" version = "1.21.4" @@ -2746,9 +2921,9 @@ checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" [[package]] name = "open" -version = "5.3.3" +version = "5.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43bb73a7fa3799b198970490a51174027ba0d4ec504b03cd08caf513d40024bc" +checksum = "9f3bab717c29a857abf75fcef718d441ec7cb2725f937343c734740a985d37fd" dependencies = [ "dunce", "is-wsl", @@ -2764,11 +2939,12 @@ checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" [[package]] name = "openvcs" -version = "0.2.2" +version = "0.3.0" dependencies = [ "base64 0.22.1", "directories", "dirs", + "dotenvy", "env_logger", "flate2", "hex", @@ -2777,6 +2953,8 @@ dependencies = [ "os_pipe", "parking_lot", "regex", + "sentry", + "sentry-log", "serde", "serde_json", "sha2 0.11.0", @@ -2791,8 +2969,8 @@ dependencies = [ "thiserror 2.0.18", "time", "tokio", - "toml 1.1.0+spec-1.1.0", - "zip 8.5.0", + "toml 1.1.2+spec-1.1.0", + "zip 8.5.1", ] [[package]] @@ -2811,6 +2989,22 @@ dependencies = [ "pin-project-lite", ] +[[package]] +name = "os_info" +version = "3.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4022a17595a00d6a369236fdae483f0de7f0a339960a53118b818238e132224" +dependencies = [ + "android_system_properties", + "log", + "nix", + "objc2", + "objc2-foundation", + "objc2-ui-kit", + "serde", + "windows-sys 0.61.2", +] + [[package]] name = "os_pipe" version = "1.2.3" @@ -2999,7 +3193,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5d5285893bb5eb82e6aaf5d59ee909a06a16737a8970984dd7746ba9283498d6" dependencies = [ "phf_shared 0.10.0", - "rand 0.8.5", + "rand 0.8.6", ] [[package]] @@ -3009,7 +3203,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d" dependencies = [ "phf_shared 0.11.3", - "rand 0.8.5", + "rand 0.8.6", ] [[package]] @@ -3104,12 +3298,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" @@ -3123,9 +3311,9 @@ dependencies = [ [[package]] name = "pkg-config" -version = "0.3.32" +version = "0.3.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" +checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e" [[package]] name = "plain" @@ -3140,7 +3328,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "740ebea15c5d1428f910cd1a5f52cebf8d25006245ed8ade92702f4943d91e07" dependencies = [ "base64 0.22.1", - "indexmap 2.13.0", + "indexmap 2.14.0", "quick-xml", "serde", "time", @@ -3181,18 +3369,18 @@ checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49" [[package]] name = "portable-atomic-util" -version = "0.2.6" +version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "091397be61a01d4be58e7841595bd4bfedb15f1cd54977d79b8271e94ed799a3" +checksum = "c2a106d1259c23fac8e543272398ae0e3c0b8d33c88ed73d0cc71b0f1d902618" dependencies = [ "portable-atomic", ] [[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", ] @@ -3260,7 +3448,7 @@ version = "3.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e67ba7e9b2b56446f1d419b1d807906278ffa1a658a8a5d8a39dcb1f5a78614f" dependencies = [ - "toml_edit 0.25.8+spec-1.1.0", + "toml_edit 0.25.11+spec-1.1.0", ] [[package]] @@ -3311,6 +3499,61 @@ dependencies = [ "memchr", ] +[[package]] +name = "quinn" +version = "0.11.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e20a958963c291dc322d98411f541009df2ced7b5a4f2bd52337638cfccf20" +dependencies = [ + "bytes", + "cfg_aliases", + "pin-project-lite", + "quinn-proto", + "quinn-udp", + "rustc-hash", + "rustls", + "socket2", + "thiserror 2.0.18", + "tokio", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-proto" +version = "0.11.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "434b42fec591c96ef50e21e886936e66d3cc3f737104fdb9b737c40ffb94c098" +dependencies = [ + "bytes", + "getrandom 0.3.4", + "lru-slab", + "rand 0.9.4", + "ring", + "rustc-hash", + "rustls", + "rustls-pki-types", + "slab", + "thiserror 2.0.18", + "tinyvec", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-udp" +version = "0.5.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "addec6a0dcad8a8d96a771f815f0eaf55f9d1805756410b39f5fa81332574cbd" +dependencies = [ + "cfg_aliases", + "libc", + "once_cell", + "socket2", + "tracing", + "windows-sys 0.60.2", +] + [[package]] name = "quote" version = "1.0.45" @@ -3348,15 +3591,25 @@ dependencies = [ [[package]] name = "rand" -version = "0.8.5" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +checksum = "5ca0ecfa931c29007047d1bc58e623ab12e5590e8c7cc53200d5202b69266d8a" dependencies = [ "libc", "rand_chacha 0.3.1", "rand_core 0.6.4", ] +[[package]] +name = "rand" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44c5af06bb1b7d3216d91932aed5265164bf384dc89cd6ba05cf59a35f5f76ea" +dependencies = [ + "rand_chacha 0.9.0", + "rand_core 0.9.5", +] + [[package]] name = "rand_chacha" version = "0.2.2" @@ -3377,6 +3630,16 @@ dependencies = [ "rand_core 0.6.4", ] +[[package]] +name = "rand_chacha" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +dependencies = [ + "ppv-lite86", + "rand_core 0.9.5", +] + [[package]] name = "rand_core" version = "0.5.1" @@ -3395,6 +3658,15 @@ dependencies = [ "getrandom 0.2.17", ] +[[package]] +name = "rand_core" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" +dependencies = [ + "getrandom 0.3.4", +] + [[package]] name = "rand_hc" version = "0.2.0" @@ -3425,16 +3697,16 @@ version = "0.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", ] [[package]] name = "redox_syscall" -version = "0.7.3" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ce70a74e890531977d37e532c34d45e9055d2409ed08ddba14529471ed0be16" +checksum = "f450ad9c3b1da563fb6948a8e0fb0fb9269711c9c73d9ea1de5058c79c8d643a" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", ] [[package]] @@ -3497,6 +3769,46 @@ version = "0.8.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" +[[package]] +name = "reqwest" +version = "0.12.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147" +dependencies = [ + "base64 0.22.1", + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-rustls", + "hyper-util", + "js-sys", + "log", + "percent-encoding", + "pin-project-lite", + "quinn", + "rustls", + "rustls-pki-types", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper", + "tokio", + "tokio-rustls", + "tower", + "tower-http", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "webpki-roots", +] + [[package]] name = "reqwest" version = "0.13.2" @@ -3574,11 +3886,17 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "rustc-demangle" +version = "0.1.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b50b8869d9fc858ce7266cce0194bd74df58b9d0e3f6df3a9fc8eb470d95c09d" + [[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" @@ -3595,7 +3913,7 @@ version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "errno", "libc", "linux-raw-sys", @@ -3604,10 +3922,11 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.37" +version = "0.23.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "758025cb5fccfd3bc2fd74708fd4682be41d99e5dff73c377c0646c6012c73a4" +checksum = "69f9466fb2c14ea04357e91413efb882e2a6d4a406e625449bc0a5d360d53a21" dependencies = [ + "log", "once_cell", "ring", "rustls-pki-types", @@ -3634,6 +3953,7 @@ version = "1.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "be040f8b0a225e40375822a563fa9524378b9d63112f53e19ffff34df5d33fdd" dependencies = [ + "web-time", "zeroize", ] @@ -3666,9 +3986,9 @@ checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f" [[package]] name = "rustls-webpki" -version = "0.103.10" +version = "0.103.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df33b2b81ac578cabaf06b89b0631153a3f416b0a886e8a7a1707fb51abbd1ef" +checksum = "61c429a8649f110dddef65e2a5ad240f747e85f7758a6bccc7e5777bd33f756e" dependencies = [ "ring", "rustls-pki-types", @@ -3681,6 +4001,12 @@ version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" +[[package]] +name = "ryu" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" + [[package]] name = "same-file" version = "1.0.6" @@ -3762,7 +4088,7 @@ version = "3.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "core-foundation", "core-foundation-sys", "libc", @@ -3803,7 +4129,7 @@ version = "0.36.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c5d9c0c92a92d33f08817311cf3f2c29a3538a8240e94a6a3c622ce652d7e00c" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "cssparser 0.36.0", "derive_more 2.1.1", "log", @@ -3818,14 +4144,123 @@ 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", ] +[[package]] +name = "sentry" +version = "0.46.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d92d893ba7469d361a6958522fa440e4e2bc8bf4c5803cd1bf40b9af63f8f9a8" +dependencies = [ + "cfg_aliases", + "httpdate", + "reqwest 0.12.28", + "rustls", + "sentry-backtrace", + "sentry-contexts", + "sentry-core", + "sentry-log", + "sentry-panic", + "sentry-tracing", + "tokio", + "ureq", +] + +[[package]] +name = "sentry-backtrace" +version = "0.46.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f8784d0a27b5cd4b5f75769ffc84f0b7580e3c35e1af9cd83cb90b612d769cc" +dependencies = [ + "backtrace", + "regex", + "sentry-core", +] + +[[package]] +name = "sentry-contexts" +version = "0.46.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e5eb42f4cd4f9fdfec9e3b07b25a4c9769df83d218a7e846658984d5948ad3e" +dependencies = [ + "hostname", + "libc", + "os_info", + "rustc_version", + "sentry-core", + "uname", +] + +[[package]] +name = "sentry-core" +version = "0.46.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0b1e7ca40f965db239da279bf278d87b7407469b98835f27f0c8e59ed189b06" +dependencies = [ + "rand 0.9.4", + "sentry-types", + "serde", + "serde_json", + "url", +] + +[[package]] +name = "sentry-log" +version = "0.46.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e200860daf76e09f9ad111bce25928f96bedbb84bc5934b37f05bb445727c70e" +dependencies = [ + "bitflags 2.11.1", + "log", + "sentry-core", +] + +[[package]] +name = "sentry-panic" +version = "0.46.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8906f8be87aea5ac7ef937323fb655d66607427f61007b99b7cb3504dc5a156c" +dependencies = [ + "sentry-backtrace", + "sentry-core", +] + +[[package]] +name = "sentry-tracing" +version = "0.46.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b07eefe04486316c57aba08ab53dd44753c25102d1d3fe05775cc93a13262d9" +dependencies = [ + "bitflags 2.11.1", + "sentry-backtrace", + "sentry-core", + "tracing-core", + "tracing-subscriber", +] + +[[package]] +name = "sentry-types" +version = "0.46.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "567711f01f86a842057e1fc17779eba33a336004227e1a1e7e6cc2599e22e259" +dependencies = [ + "debugid", + "hex", + "rand 0.9.4", + "serde", + "serde_json", + "thiserror 2.0.18", + "time", + "url", + "uuid", +] + [[package]] name = "serde" version = "1.0.228" @@ -3914,13 +4349,25 @@ dependencies = [ [[package]] name = "serde_spanned" -version = "1.1.0" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "876ac351060d4f882bb1032b6369eb0aef79ad9df1ea8bc404874d8cc3d0cd98" +checksum = "6662b5879511e06e8999a8a235d848113e942c9124f211511b16466ee2995f26" dependencies = [ "serde_core", ] +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + [[package]] name = "serde_with" version = "3.18.0" @@ -3931,7 +4378,7 @@ dependencies = [ "chrono", "hex", "indexmap 1.9.3", - "indexmap 2.13.0", + "indexmap 2.14.0", "schemars 0.9.0", "schemars 1.2.1", "serde_core", @@ -4044,9 +4491,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 = "siphasher" @@ -4269,7 +4716,7 @@ version = "0.34.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9103edf55f2da3c82aea4c7fab7c4241032bfeea0e71fa557d98e00e7ce7cc20" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "block2", "core-foundation", "core-graphics", @@ -4359,7 +4806,7 @@ dependencies = [ "percent-encoding", "plist", "raw-window-handle", - "reqwest", + "reqwest 0.13.2", "serde", "serde_json", "serde_repr", @@ -4540,7 +4987,7 @@ dependencies = [ "minisign-verify", "osakit", "percent-encoding", - "reqwest", + "reqwest 0.13.2", "rustls", "semver", "serde", @@ -4767,19 +5214,34 @@ 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", ] +[[package]] +name = "tinyvec" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e61e67053d25a4e82c844e8424039d9745781b3fc4f32b8d55ed50f5f667ef3" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + [[package]] name = "tokio" -version = "1.51.0" +version = "1.52.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2bd1c4c0fc4a7ab90fc15ef6daaa3ec3b893f004f915f2392557ed23237820cd" +checksum = "b67dee974fe86fd92cc45b7a95fdd2f99a36a6d7b0d431a231178d3d670bbcc6" dependencies = [ "bytes", "libc", @@ -4831,9 +5293,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.14.0", "serde_core", - "serde_spanned 1.1.0", + "serde_spanned 1.1.1", "toml_datetime 0.7.5+spec-1.1.0", "toml_parser", "toml_writer", @@ -4842,17 +5304,17 @@ dependencies = [ [[package]] name = "toml" -version = "1.1.0+spec-1.1.0" +version = "1.1.2+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8195ca05e4eb728f4ba94f3e3291661320af739c4e43779cbdfae82ab239fcc" +checksum = "81f3d15e84cbcd896376e6730314d59fb5a87f31e4b038454184435cd57defee" dependencies = [ - "indexmap 2.13.0", + "indexmap 2.14.0", "serde_core", - "serde_spanned 1.1.0", - "toml_datetime 1.1.0+spec-1.1.0", + "serde_spanned 1.1.1", + "toml_datetime 1.1.1+spec-1.1.0", "toml_parser", "toml_writer", - "winnow 1.0.0", + "winnow 1.0.2", ] [[package]] @@ -4875,9 +5337,9 @@ dependencies = [ [[package]] name = "toml_datetime" -version = "1.1.0+spec-1.1.0" +version = "1.1.1+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97251a7c317e03ad83774a8752a7e81fb6067740609f75ea2b585b569a59198f" +checksum = "3165f65f62e28e0115a00b2ebdd37eb6f3b641855f9d636d3cd4103767159ad7" dependencies = [ "serde_core", ] @@ -4888,7 +5350,7 @@ version = "0.19.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" dependencies = [ - "indexmap 2.13.0", + "indexmap 2.14.0", "toml_datetime 0.6.3", "winnow 0.5.40", ] @@ -4899,7 +5361,7 @@ version = "0.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "396e4d48bbb2b7554c944bde63101b5ae446cff6ec4a24227428f15eb72ef338" dependencies = [ - "indexmap 2.13.0", + "indexmap 2.14.0", "serde", "serde_spanned 0.6.9", "toml_datetime 0.6.3", @@ -4908,30 +5370,30 @@ dependencies = [ [[package]] name = "toml_edit" -version = "0.25.8+spec-1.1.0" +version = "0.25.11+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16bff38f1d86c47f9ff0647e6838d7bb362522bdf44006c7068c2b1e606f1f3c" +checksum = "0b59c4d22ed448339746c59b905d24568fcbb3ab65a500494f7b8c3e97739f2b" dependencies = [ - "indexmap 2.13.0", - "toml_datetime 1.1.0+spec-1.1.0", + "indexmap 2.14.0", + "toml_datetime 1.1.1+spec-1.1.0", "toml_parser", - "winnow 1.0.0", + "winnow 1.0.2", ] [[package]] name = "toml_parser" -version = "1.1.0+spec-1.1.0" +version = "1.1.2+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2334f11ee363607eb04df9b8fc8a13ca1715a72ba8662a26ac285c98aabb4011" +checksum = "a2abe9b86193656635d2411dc43050282ca48aa31c2451210f4202550afb7526" dependencies = [ - "winnow 1.0.0", + "winnow 1.0.2", ] [[package]] name = "toml_writer" -version = "1.1.0+spec-1.1.0" +version = "1.1.1+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d282ade6016312faf3e41e57ebbba0c073e4056dab1232ab1cb624199648f8ed" +checksum = "756daf9b1013ebe47a8776667b466417e2d4c5679d441c26230efd9ef78692db" [[package]] name = "tower" @@ -4954,7 +5416,7 @@ version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d4e6559d53cc268e5031cd8429d05415bc4cb4aefc4aa5d6cc35fbf5b924a1f8" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "bytes", "futures-util", "http", @@ -5007,6 +5469,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" dependencies = [ "once_cell", + "valuable", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb7f578e5945fb242538965c2d0b04418d38ec25c79d160cd279bf0731c8d319" +dependencies = [ + "tracing-core", ] [[package]] @@ -5051,9 +5523,9 @@ checksum = "bc7d623258602320d5c55d1bc22793b57daff0ec7efc270ea7d55ce1d5f5471c" [[package]] name = "typenum" -version = "1.19.0" +version = "1.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" +checksum = "40ce102ab67701b8526c123c1bab5cbe42d7040ccfd0f64af1a385808d2f43de" [[package]] name = "uds_windows" @@ -5066,6 +5538,15 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "uname" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b72f89f0ca32e4db1c04e2a72f5345d59796d4866a1ee0609084569f73683dc8" +dependencies = [ + "libc", +] + [[package]] name = "unic-char-property" version = "0.9.0" @@ -5115,9 +5596,9 @@ checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" [[package]] name = "unicode-segmentation" -version = "1.13.1" +version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da36089a805484bcccfffe0739803392c8298778a2d2f09febf76fac5ad9025b" +checksum = "9629274872b2bfaf8d66f5f15725007f635594914870f65218920345aa11aa8c" [[package]] name = "unicode-xid" @@ -5131,6 +5612,34 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" +[[package]] +name = "ureq" +version = "3.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dea7109cdcd5864d4eeb1b58a1648dc9bf520360d7af16ec26d0a9354bafcfc0" +dependencies = [ + "base64 0.22.1", + "log", + "percent-encoding", + "rustls", + "rustls-pki-types", + "ureq-proto", + "utf8-zero", + "webpki-roots", +] + +[[package]] +name = "ureq-proto" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e994ba84b0bd1b1b0cf92878b7ef898a5c1760108fe7b6010327e274917a808c" +dependencies = [ + "base64 0.22.1", + "http", + "httparse", + "log", +] + [[package]] name = "url" version = "2.5.8" @@ -5162,6 +5671,12 @@ version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" +[[package]] +name = "utf8-zero" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8c0a043c9540bae7c578c88f91dda8bd82e59ae27c21baca69c8b191aaf5a6e" + [[package]] name = "utf8_iter" version = "1.0.4" @@ -5176,9 +5691,9 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "uuid" -version = "1.22.0" +version = "1.23.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a68d3c8f01c0cfa54a75291d83601161799e4a89a39e0929f4b0354d88757a37" +checksum = "ddd74a9687298c6858e9b88ec8935ec45d22e8fd5e6394fa1bd4e99a87789c76" dependencies = [ "getrandom 0.4.2", "js-sys", @@ -5186,6 +5701,12 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "valuable" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" + [[package]] name = "version-compare" version = "0.2.1" @@ -5251,11 +5772,11 @@ checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" [[package]] name = "wasip2" -version = "1.0.2+wasi-0.2.9" +version = "1.0.3+wasi-0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5" +checksum = "20064672db26d7cdc89c7798c48a0fdfac8213434a1186e5ef29fd560ae223d6" dependencies = [ - "wit-bindgen", + "wit-bindgen 0.57.1", ] [[package]] @@ -5264,14 +5785,14 @@ version = "0.4.0+wasi-0.3.0-rc-2026-01-06" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5" dependencies = [ - "wit-bindgen", + "wit-bindgen 0.51.0", ] [[package]] name = "wasm-bindgen" -version = "0.2.114" +version = "0.2.118" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6532f9a5c1ece3798cb1c2cfdba640b9b3ba884f5db45973a6f442510a87d38e" +checksum = "0bf938a0bacb0469e83c1e148908bd7d5a6010354cf4fb73279b7447422e3a89" dependencies = [ "cfg-if", "once_cell", @@ -5282,23 +5803,19 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.64" +version = "0.4.68" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e9c5522b3a28661442748e09d40924dfb9ca614b21c00d3fd135720e48b67db8" +checksum = "f371d383f2fb139252e0bfac3b81b265689bf45b6874af544ffa4c975ac1ebf8" 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.118" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18a2d50fcf105fb33bb15f00e7a77b772945a2ee45dcf454961fd843e74c18e6" +checksum = "eeff24f84126c0ec2db7a449f0c2ec963c6a49efe0698c4242929da037ca28ed" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -5306,9 +5823,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.114" +version = "0.2.118" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03ce4caeaac547cdf713d280eda22a730824dd11e6b8c3ca9e42247b25c631e3" +checksum = "9d08065faf983b2b80a79fd87d8254c409281cf7de75fc4b773019824196c904" dependencies = [ "bumpalo", "proc-macro2", @@ -5319,9 +5836,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.114" +version = "0.2.118" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75a326b8c223ee17883a4251907455a2431acc2791c98c26279376490c378c16" +checksum = "5fd04d9e306f1907bd13c6361b5c6bfc7b3b3c095ed3f8a9246390f8dbdee129" dependencies = [ "unicode-ident", ] @@ -5343,7 +5860,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909" dependencies = [ "anyhow", - "indexmap 2.13.0", + "indexmap 2.14.0", "wasm-encoder", "wasmparser", ] @@ -5367,17 +5884,27 @@ version = "0.244.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "hashbrown 0.15.5", - "indexmap 2.13.0", + "indexmap 2.14.0", "semver", ] [[package]] name = "web-sys" -version = "0.3.91" +version = "0.3.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "854ba17bb104abfb26ba36da9729addc7ce7f06f5c0f90f3c391f8461cca21f9" +checksum = "4f2dfbb17949fa2088e5d39408c48368947b86f7834484e87b73de55bc14d97d" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "web-time" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" dependencies = [ "js-sys", "wasm-bindgen", @@ -5385,9 +5912,9 @@ dependencies = [ [[package]] name = "web_atoms" -version = "0.2.3" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57a9779e9f04d2ac1ce317aee707aa2f6b773afba7b931222bff6983843b1576" +checksum = "d7cff6eef815df1834fd250e3a2ff436044d82a9f1bc1980ca1dbdf07effc538" dependencies = [ "phf 0.13.1", "phf_codegen 0.13.1", @@ -5441,9 +5968,18 @@ dependencies = [ [[package]] name = "webpki-root-certs" -version = "1.0.6" +version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "804f18a4ac2676ffb4e8b5b5fa9ae38af06df08162314f96a68d2a363e21a8ca" +checksum = "f31141ce3fc3e300ae89b78c0dd67f9708061d1d2eda54b8209346fd6be9a92c" +dependencies = [ + "rustls-pki-types", +] + +[[package]] +name = "webpki-roots" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52f5ee44c96cf55f1b349600768e3ece3a8f26010c05265ab73f945bb1a2eb9d" dependencies = [ "rustls-pki-types", ] @@ -5938,9 +6474,9 @@ dependencies = [ [[package]] name = "winnow" -version = "1.0.0" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a90e88e4667264a994d34e6d1ab2d26d398dcdca8b7f52bec8668957517fc7d8" +checksum = "2ee1708bef14716a11bae175f579062d4554d95be2c6829f518df847b7b3fdd0" dependencies = [ "memchr", ] @@ -5964,6 +6500,12 @@ dependencies = [ "wit-bindgen-rust-macro", ] +[[package]] +name = "wit-bindgen" +version = "0.57.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" + [[package]] name = "wit-bindgen-core" version = "0.51.0" @@ -5983,7 +6525,7 @@ checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21" dependencies = [ "anyhow", "heck 0.5.0", - "indexmap 2.13.0", + "indexmap 2.14.0", "prettyplease", "syn 2.0.117", "wasm-metadata", @@ -6013,8 +6555,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" dependencies = [ "anyhow", - "bitflags 2.11.0", - "indexmap 2.13.0", + "bitflags 2.11.1", + "indexmap 2.14.0", "log", "serde", "serde_derive", @@ -6033,7 +6575,7 @@ checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736" dependencies = [ "anyhow", "id-arena", - "indexmap 2.13.0", + "indexmap 2.14.0", "log", "semver", "serde", @@ -6045,9 +6587,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" @@ -6126,9 +6668,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", @@ -6137,9 +6679,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", @@ -6210,18 +6752,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", @@ -6230,18 +6772,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", @@ -6257,9 +6799,9 @@ checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" [[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", @@ -6268,9 +6810,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", @@ -6279,9 +6821,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", @@ -6296,15 +6838,15 @@ checksum = "caa8cd6af31c3b31c6631b8f483848b91589021b28fffe50adada48d4f4d2ed1" dependencies = [ "arbitrary", "crc32fast", - "indexmap 2.13.0", + "indexmap 2.14.0", "memchr", ] [[package]] name = "zip" -version = "8.5.0" +version = "8.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2726508a48f38dceb22b35ecbbd2430efe34ff05c62bd3285f965d7911b33464" +checksum = "dcab981e19633ebcf0b001ddd37dd802996098bc1864f90b7c5d970ce76c1d59" dependencies = [ "aes", "bzip2", @@ -6314,7 +6856,7 @@ dependencies = [ "flate2", "getrandom 0.4.2", "hmac", - "indexmap 2.13.0", + "indexmap 2.14.0", "lzma-rust2", "memchr", "pbkdf2", diff --git a/Frontend/package-lock.json b/Frontend/package-lock.json index 1f2889d8..8823ee7d 100644 --- a/Frontend/package-lock.json +++ b/Frontend/package-lock.json @@ -9,50 +9,61 @@ "version": "1.0.0", "license": "ISC", "dependencies": { - "overlayscrollbars": "^2.14.0" + "overlayscrollbars": "^2.15.1" }, "devDependencies": { - "@types/node": "^25.5.2", - "jsdom": "^29.0.1", - "typescript": "^6.0.2", - "vite": "^8.0.5", - "vitest": "^4.1.2" + "@sentry/vite-plugin": "^4.5.0", + "@types/node": "^25.6.0", + "jsdom": "^29.0.2", + "typescript": "^6.0.3", + "vite": "^8.0.9", + "vitest": "^4.1.4" } }, "node_modules/@asamuzakjp/css-color": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-5.0.1.tgz", - "integrity": "sha512-2SZFvqMyvboVV1d15lMf7XiI3m7SDqXUuKaTymJYLN6dSGadqp+fVojqJlVoMlbZnlTmu3S0TLwLTJpvBMO1Aw==", + "version": "5.1.11", + "resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-5.1.11.tgz", + "integrity": "sha512-KVw6qIiCTUQhByfTd78h2yD1/00waTmm9uy/R7Ck/ctUyAPj+AEDLkQIdJW0T8+qGgj3j5bpNKK7Q3G+LedJWg==", "dev": true, "license": "MIT", "dependencies": { - "@csstools/css-calc": "^3.1.1", - "@csstools/css-color-parser": "^4.0.2", + "@asamuzakjp/generational-cache": "^1.0.1", + "@csstools/css-calc": "^3.2.0", + "@csstools/css-color-parser": "^4.1.0", "@csstools/css-parser-algorithms": "^4.0.0", - "@csstools/css-tokenizer": "^4.0.0", - "lru-cache": "^11.2.6" + "@csstools/css-tokenizer": "^4.0.0" }, "engines": { "node": "^20.19.0 || ^22.12.0 || >=24.0.0" } }, "node_modules/@asamuzakjp/dom-selector": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/@asamuzakjp/dom-selector/-/dom-selector-7.0.3.tgz", - "integrity": "sha512-Q6mU0Z6bfj6YvnX2k9n0JxiIwrCFN59x/nWmYQnAqP000ruX/yV+5bp/GRcF5T8ncvfwJQ7fgfP74DlpKExILA==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/@asamuzakjp/dom-selector/-/dom-selector-7.1.1.tgz", + "integrity": "sha512-67RZDnYRc8H/8MLDgQCDE//zoqVFwajkepHZgmXrbwybzXOEwOWGPYGmALYl9J2DOLfFPPs6kKCqmbzV895hTQ==", "dev": true, "license": "MIT", "dependencies": { + "@asamuzakjp/generational-cache": "^1.0.1", "@asamuzakjp/nwsapi": "^2.3.9", "bidi-js": "^1.0.3", "css-tree": "^3.2.1", - "is-potential-custom-element-name": "^1.0.1", - "lru-cache": "^11.2.7" + "is-potential-custom-element-name": "^1.0.1" }, "engines": { "node": "^20.19.0 || ^22.12.0 || >=24.0.0" } }, + "node_modules/@asamuzakjp/generational-cache": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@asamuzakjp/generational-cache/-/generational-cache-1.0.1.tgz", + "integrity": "sha512-wajfB8KqzMCN2KGNFdLkReeHncd0AslUSrvHVvvYWuU8ghncRJoA50kT3zP9MVL0+9g4/67H+cdvBskj9THPzg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + } + }, "node_modules/@asamuzakjp/nwsapi": { "version": "2.3.9", "resolved": "https://registry.npmjs.org/@asamuzakjp/nwsapi/-/nwsapi-2.3.9.tgz", @@ -60,6 +71,246 @@ "dev": true, "license": "MIT" }, + "node_modules/@babel/code-frame": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz", + "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.28.5", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.0.tgz", + "integrity": "sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.0.tgz", + "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", + "dev": true, + "license": "MIT", + "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" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/generator": { + "version": "7.29.1", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.1.tgz", + "integrity": "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==", + "dev": true, + "license": "MIT", + "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" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz", + "integrity": "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==", + "dev": true, + "license": "MIT", + "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" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-globals": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", + "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz", + "integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz", + "integrity": "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.28.6", + "@babel/helper-validator-identifier": "^7.28.5", + "@babel/traverse": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", + "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.29.2", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.2.tgz", + "integrity": "sha512-HoGuUs4sCZNezVEKdVcwqmZN8GoHirLUcLaYVNBK2J0DadGtdcqgr3BCbvH8+XUo4NGjNl3VOtSjEKNzqfFgKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.28.6", + "@babel/types": "^7.29.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.2", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.2.tgz", + "integrity": "sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.0" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/template": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz", + "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.28.6", + "@babel/parser": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.0.tgz", + "integrity": "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==", + "dev": true, + "license": "MIT", + "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" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", + "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@bramus/specificity": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/@bramus/specificity/-/specificity-2.4.2.tgz", @@ -94,9 +345,9 @@ } }, "node_modules/@csstools/css-calc": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-3.1.1.tgz", - "integrity": "sha512-HJ26Z/vmsZQqs/o3a6bgKslXGFAungXGbinULZO3eMsOyNJHeBBZfup5FiZInOghgoM4Hwnmw+OgbJCNg1wwUQ==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-3.2.0.tgz", + "integrity": "sha512-bR9e6o2BDB12jzN/gIbjHa5wLJ4UjD1CB9pM7ehlc0ddk6EBz+yYS1EV2MF55/HUxrHcB/hehAyt5vhsA3hx7w==", "dev": true, "funding": [ { @@ -118,9 +369,9 @@ } }, "node_modules/@csstools/css-color-parser": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-4.0.2.tgz", - "integrity": "sha512-0GEfbBLmTFf0dJlpsNU7zwxRIH0/BGEMuXLTCvFYxuL1tNhqzTbtnFICyJLTNK4a+RechKP75e7w42ClXSnJQw==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-4.1.0.tgz", + "integrity": "sha512-U0KhLYmy2GVj6q4T3WaAe6NPuFYCPQoE3b0dRGxejWDgcPp8TP7S5rVdM5ZrFaqu4N67X8YaPBw14dQSYx3IyQ==", "dev": true, "funding": [ { @@ -135,7 +386,7 @@ "license": "MIT", "dependencies": { "@csstools/color-helpers": "^6.0.2", - "@csstools/css-calc": "^3.1.1" + "@csstools/css-calc": "^3.2.0" }, "engines": { "node": ">=20.19.0" @@ -169,9 +420,9 @@ } }, "node_modules/@csstools/css-syntax-patches-for-csstree": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@csstools/css-syntax-patches-for-csstree/-/css-syntax-patches-for-csstree-1.1.1.tgz", - "integrity": "sha512-BvqN0AMWNAnLk9G8jnUT77D+mUbY/H2b3uDTvg2isJkHaOufUE2R3AOwxWo7VBQKT1lOdwdvorddo2B/lk64+w==", + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@csstools/css-syntax-patches-for-csstree/-/css-syntax-patches-for-csstree-1.1.3.tgz", + "integrity": "sha512-SH60bMfrRCJF3morcdk57WklujF4Jr/EsQUzqkarfHXEFcAR1gg7fS/chAE922Sehgzc1/+Tz5H3Ypa1HiEKrg==", "dev": true, "funding": [ { @@ -214,38 +465,35 @@ } }, "node_modules/@emnapi/core": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.9.1.tgz", - "integrity": "sha512-mukuNALVsoix/w1BJwFzwXBN/dHeejQtuVzcDsfOEsdpCumXb/E9j8w11h5S54tT1xhifGfbbSm/ICrObRb3KA==", + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.9.2.tgz", + "integrity": "sha512-UC+ZhH3XtczQYfOlu3lNEkdW/p4dsJ1r/bP7H8+rhao3TTTMO1ATq/4DdIi23XuGoFY+Cz0JmCbdVl0hz9jZcA==", "dev": true, "license": "MIT", "optional": true, - "peer": true, "dependencies": { - "@emnapi/wasi-threads": "1.2.0", + "@emnapi/wasi-threads": "1.2.1", "tslib": "^2.4.0" } }, "node_modules/@emnapi/runtime": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.9.1.tgz", - "integrity": "sha512-VYi5+ZVLhpgK4hQ0TAjiQiZ6ol0oe4mBx7mVv7IflsiEp0OWoVsp/+f9Vc1hOhE0TtkORVrI1GvzyreqpgWtkA==", + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.9.2.tgz", + "integrity": "sha512-3U4+MIWHImeyu1wnmVygh5WlgfYDtyf0k8AbLhMFxOipihf6nrWC4syIm/SwEeec0mNSafiiNnMJwbza/Is6Lw==", "dev": true, "license": "MIT", "optional": true, - "peer": true, "dependencies": { "tslib": "^2.4.0" } }, "node_modules/@emnapi/wasi-threads": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.0.tgz", - "integrity": "sha512-N10dEJNSsUx41Z6pZsXU8FjPjpBEplgH24sfkmITrBED1/U2Esum9F3lfLrMjKHHjmi557zQn7kR9R+XWXu5Rg==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.1.tgz", + "integrity": "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==", "dev": true, "license": "MIT", "optional": true, - "peer": true, "dependencies": { "tslib": "^2.4.0" } @@ -268,6 +516,56 @@ } } }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "dev": true, + "license": "ISC", + "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" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/@jridgewell/sourcemap-codec": { "version": "1.5.5", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", @@ -275,10 +573,21 @@ "dev": true, "license": "MIT" }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, "node_modules/@napi-rs/wasm-runtime": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.2.tgz", - "integrity": "sha512-sNXv5oLJ7ob93xkZ1XnxisYhGYXfaG9f65/ZgYuAu3qt7b3NadcOEhLvx28hv31PgX8SZJRYrAIPQilQmFpLVw==", + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.4.tgz", + "integrity": "sha512-3NQNNgA1YSlJb/kMH1ildASP9HW7/7kYnRI2szWJaofaS1hWmbGI4H+d3+22aGzXXN9IJ+n+GiFVcGipJP18ow==", "dev": true, "license": "MIT", "optional": true, @@ -295,19 +604,30 @@ } }, "node_modules/@oxc-project/types": { - "version": "0.122.0", - "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.122.0.tgz", - "integrity": "sha512-oLAl5kBpV4w69UtFZ9xqcmTi+GENWOcPF7FCrczTiBbmC0ibXxCwyvZGbO39rCVEuLGAZM84DH0pUIyyv/YJzA==", + "version": "0.126.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.126.0.tgz", + "integrity": "sha512-oGfVtjAgwQVVpfBrbtk4e1XDyWHRFta6BS3GWVzrF8xYBT2VGQAk39yJS/wFSMrZqoiCU4oghT3Ch0HaHGIHcQ==", "dev": true, "license": "MIT", "funding": { "url": "https://github.com/sponsors/Boshen" } }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=14" + } + }, "node_modules/@rolldown/binding-android-arm64": { - "version": "1.0.0-rc.12", - "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.0-rc.12.tgz", - "integrity": "sha512-pv1y2Fv0JybcykuiiD3qBOBdz6RteYojRFY1d+b95WVuzx211CRh+ytI/+9iVyWQ6koTh5dawe4S/yRfOFjgaA==", + "version": "1.0.0-rc.16", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.0-rc.16.tgz", + "integrity": "sha512-rhY3k7Bsae9qQfOtph2Pm2jZEA+s8Gmjoz4hhmx70K9iMQ/ddeae+xhRQcM5IuVx5ry1+bGfkvMn7D6MJggVSA==", "cpu": [ "arm64" ], @@ -322,9 +642,9 @@ } }, "node_modules/@rolldown/binding-darwin-arm64": { - "version": "1.0.0-rc.12", - "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.0-rc.12.tgz", - "integrity": "sha512-cFYr6zTG/3PXXF3pUO+umXxt1wkRK/0AYT8lDwuqvRC+LuKYWSAQAQZjCWDQpAH172ZV6ieYrNnFzVVcnSflAg==", + "version": "1.0.0-rc.16", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.0-rc.16.tgz", + "integrity": "sha512-rNz0yK078yrNn3DrdgN+PKiMOW8HfQ92jQiXxwX8yW899ayV00MLVdaCNeVBhG/TbH3ouYVObo8/yrkiectkcQ==", "cpu": [ "arm64" ], @@ -339,9 +659,9 @@ } }, "node_modules/@rolldown/binding-darwin-x64": { - "version": "1.0.0-rc.12", - "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.0-rc.12.tgz", - "integrity": "sha512-ZCsYknnHzeXYps0lGBz8JrF37GpE9bFVefrlmDrAQhOEi4IOIlcoU1+FwHEtyXGx2VkYAvhu7dyBf75EJQffBw==", + "version": "1.0.0-rc.16", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.0-rc.16.tgz", + "integrity": "sha512-r/OmdR00HmD4i79Z//xO06uEPOq5hRXdhw7nzkxQxwSavs3PSHa1ijntdpOiZ2mzOQ3fVVu8C1M19FoNM+dMUQ==", "cpu": [ "x64" ], @@ -356,9 +676,9 @@ } }, "node_modules/@rolldown/binding-freebsd-x64": { - "version": "1.0.0-rc.12", - "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.0-rc.12.tgz", - "integrity": "sha512-dMLeprcVsyJsKolRXyoTH3NL6qtsT0Y2xeuEA8WQJquWFXkEC4bcu1rLZZSnZRMtAqwtrF/Ib9Ddtpa/Gkge9Q==", + "version": "1.0.0-rc.16", + "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.0-rc.16.tgz", + "integrity": "sha512-KcRE5w8h0OnjUatG8pldyD14/CQ5Phs1oxfR+3pKDjboHRo9+MkqQaiIZlZRpsxC15paeXme/I127tUa9TXJ6g==", "cpu": [ "x64" ], @@ -373,9 +693,9 @@ } }, "node_modules/@rolldown/binding-linux-arm-gnueabihf": { - "version": "1.0.0-rc.12", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.0-rc.12.tgz", - "integrity": "sha512-YqWjAgGC/9M1lz3GR1r1rP79nMgo3mQiiA+Hfo+pvKFK1fAJ1bCi0ZQVh8noOqNacuY1qIcfyVfP6HoyBRZ85Q==", + "version": "1.0.0-rc.16", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.0-rc.16.tgz", + "integrity": "sha512-bT0guA1bpxEJ/ZhTRniQf7rNF8ybvXOuWbNIeLABaV5NGjx4EtOWBTSRGWFU9ZWVkPOZ+HNFP8RMcBokBiZ0Kg==", "cpu": [ "arm" ], @@ -390,9 +710,9 @@ } }, "node_modules/@rolldown/binding-linux-arm64-gnu": { - "version": "1.0.0-rc.12", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.0-rc.12.tgz", - "integrity": "sha512-/I5AS4cIroLpslsmzXfwbe5OmWvSsrFuEw3mwvbQ1kDxJ822hFHIx+vsN/TAzNVyepI/j/GSzrtCIwQPeKCLIg==", + "version": "1.0.0-rc.16", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.0-rc.16.tgz", + "integrity": "sha512-+tHktCHWV8BDQSjemUqm/Jl/TPk3QObCTIjmdDy/nlupcujZghmKK2962LYrqFpWu+ai01AN/REOH3NEpqvYQg==", "cpu": [ "arm64" ], @@ -407,9 +727,9 @@ } }, "node_modules/@rolldown/binding-linux-arm64-musl": { - "version": "1.0.0-rc.12", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.0-rc.12.tgz", - "integrity": "sha512-V6/wZztnBqlx5hJQqNWwFdxIKN0m38p8Jas+VoSfgH54HSj9tKTt1dZvG6JRHcjh6D7TvrJPWFGaY9UBVOaWPw==", + "version": "1.0.0-rc.16", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.0-rc.16.tgz", + "integrity": "sha512-3fPzdREH806oRLxpTWW1Gt4tQHs0TitZFOECB2xzCFLPKnSOy90gwA7P29cksYilFO6XVRY1kzga0cL2nRjKPg==", "cpu": [ "arm64" ], @@ -424,9 +744,9 @@ } }, "node_modules/@rolldown/binding-linux-ppc64-gnu": { - "version": "1.0.0-rc.12", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.0-rc.12.tgz", - "integrity": "sha512-AP3E9BpcUYliZCxa3w5Kwj9OtEVDYK6sVoUzy4vTOJsjPOgdaJZKFmN4oOlX0Wp0RPV2ETfmIra9x1xuayFB7g==", + "version": "1.0.0-rc.16", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.0-rc.16.tgz", + "integrity": "sha512-EKwI1tSrLs7YVw+JPJT/G2dJQ1jl9qlTTTEG0V2Ok/RdOenRfBw2PQdLPyjhIu58ocdBfP7vIRN/pvMsPxs/AQ==", "cpu": [ "ppc64" ], @@ -441,9 +761,9 @@ } }, "node_modules/@rolldown/binding-linux-s390x-gnu": { - "version": "1.0.0-rc.12", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.0-rc.12.tgz", - "integrity": "sha512-nWwpvUSPkoFmZo0kQazZYOrT7J5DGOJ/+QHHzjvNlooDZED8oH82Yg67HvehPPLAg5fUff7TfWFHQS8IV1n3og==", + "version": "1.0.0-rc.16", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.0-rc.16.tgz", + "integrity": "sha512-Uknladnb3Sxqu6SEcqBldQyJUpk8NleooZEc0MbRBJ4inEhRYWZX0NJu12vNf2mqAq7gsofAxHrGghiUYjhaLQ==", "cpu": [ "s390x" ], @@ -458,9 +778,9 @@ } }, "node_modules/@rolldown/binding-linux-x64-gnu": { - "version": "1.0.0-rc.12", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.0-rc.12.tgz", - "integrity": "sha512-RNrafz5bcwRy+O9e6P8Z/OCAJW/A+qtBczIqVYwTs14pf4iV1/+eKEjdOUta93q2TsT/FI0XYDP3TCky38LMAg==", + "version": "1.0.0-rc.16", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.0-rc.16.tgz", + "integrity": "sha512-FIb8+uG49sZBtLTn+zt1AJ20TqVcqWeSIyoVt0or7uAWesgKaHbiBh6OpA/k9v0LTt+PTrb1Lao133kP4uVxkg==", "cpu": [ "x64" ], @@ -475,9 +795,9 @@ } }, "node_modules/@rolldown/binding-linux-x64-musl": { - "version": "1.0.0-rc.12", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.0-rc.12.tgz", - "integrity": "sha512-Jpw/0iwoKWx3LJ2rc1yjFrj+T7iHZn2JDg1Yny1ma0luviFS4mhAIcd1LFNxK3EYu3DHWCps0ydXQ5i/rrJ2ig==", + "version": "1.0.0-rc.16", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.0-rc.16.tgz", + "integrity": "sha512-RuERhF9/EgWxZEXYWCOaViUWHIboceK4/ivdtQ3R0T44NjLkIIlGIAVAuCddFxsZ7vnRHtNQUrt2vR2n2slB2w==", "cpu": [ "x64" ], @@ -492,9 +812,9 @@ } }, "node_modules/@rolldown/binding-openharmony-arm64": { - "version": "1.0.0-rc.12", - "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.0-rc.12.tgz", - "integrity": "sha512-vRugONE4yMfVn0+7lUKdKvN4D5YusEiPilaoO2sgUWpCvrncvWgPMzK00ZFFJuiPgLwgFNP5eSiUlv2tfc+lpA==", + "version": "1.0.0-rc.16", + "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.0-rc.16.tgz", + "integrity": "sha512-mXcXnvd9GpazCxeUCCnZ2+YF7nut+ZOEbE4GtaiPtyY6AkhZWbK70y1KK3j+RDhjVq5+U8FySkKRb/+w0EeUwA==", "cpu": [ "arm64" ], @@ -509,9 +829,9 @@ } }, "node_modules/@rolldown/binding-wasm32-wasi": { - "version": "1.0.0-rc.12", - "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.0-rc.12.tgz", - "integrity": "sha512-ykGiLr/6kkiHc0XnBfmFJuCjr5ZYKKofkx+chJWDjitX+KsJuAmrzWhwyOMSHzPhzOHOy7u9HlFoa5MoAOJ/Zg==", + "version": "1.0.0-rc.16", + "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.0-rc.16.tgz", + "integrity": "sha512-3Q2KQxnC8IJOLqXmUMoYwyIPZU9hzRbnHaoV3Euz+VVnjZKcY8ktnNP8T9R4/GGQtb27C/UYKABxesKWb8lsvQ==", "cpu": [ "wasm32" ], @@ -519,16 +839,18 @@ "license": "MIT", "optional": true, "dependencies": { - "@napi-rs/wasm-runtime": "^1.1.1" + "@emnapi/core": "1.9.2", + "@emnapi/runtime": "1.9.2", + "@napi-rs/wasm-runtime": "^1.1.4" }, "engines": { - "node": ">=14.0.0" + "node": "^20.19.0 || >=22.12.0" } }, "node_modules/@rolldown/binding-win32-arm64-msvc": { - "version": "1.0.0-rc.12", - "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.0-rc.12.tgz", - "integrity": "sha512-5eOND4duWkwx1AzCxadcOrNeighiLwMInEADT0YM7xeEOOFcovWZCq8dadXgcRHSf3Ulh1kFo/qvzoFiCLOL1Q==", + "version": "1.0.0-rc.16", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.0-rc.16.tgz", + "integrity": "sha512-tj7XRemQcOcFwv7qhpUxMTBbI5mWMlE4c1Omhg5+h8GuLXzyj8HviYgR+bB2DMDgRqUE+jiDleqSCRjx4aYk/Q==", "cpu": [ "arm64" ], @@ -543,9 +865,9 @@ } }, "node_modules/@rolldown/binding-win32-x64-msvc": { - "version": "1.0.0-rc.12", - "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.0-rc.12.tgz", - "integrity": "sha512-PyqoipaswDLAZtot351MLhrlrh6lcZPo2LSYE+VDxbVk24LVKAGOuE4hb8xZQmrPAuEtTZW8E6D2zc5EUZX4Lw==", + "version": "1.0.0-rc.16", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.0-rc.16.tgz", + "integrity": "sha512-PH5DRZT+F4f2PTXRXR8uJxnBq2po/xFtddyabTJVJs/ZYVHqXPEgNIr35IHTEa6bpa0Q8Awg+ymkTaGnKITw4g==", "cpu": [ "x64" ], @@ -560,91 +882,309 @@ } }, "node_modules/@rolldown/pluginutils": { - "version": "1.0.0-rc.12", - "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.12.tgz", - "integrity": "sha512-HHMwmarRKvoFsJorqYlFeFRzXZqCt2ETQlEDOb9aqssrnVBB1/+xgTGtuTrIk5vzLNX1MjMtTf7W9z3tsSbrxw==", + "version": "1.0.0-rc.16", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.16.tgz", + "integrity": "sha512-45+YtqxLYKDWQouLKCrpIZhke+nXxhsw+qAHVzHDVwttyBlHNBVs2K25rDXrZzhpTp9w1FlAlvweV1H++fdZoA==", "dev": true, "license": "MIT" }, - "node_modules/@standard-schema/spec": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", - "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", + "node_modules/@sentry/babel-plugin-component-annotate": { + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/@sentry/babel-plugin-component-annotate/-/babel-plugin-component-annotate-4.9.1.tgz", + "integrity": "sha512-0gEoi2Lb54MFYPOmdTfxlNKxI7kCOvNV7gP8lxMXJ7nCazF5OqOOZIVshfWjDLrc0QrSV6XdVvwPV9GDn4wBMg==", "dev": true, - "license": "MIT" + "license": "MIT", + "engines": { + "node": ">= 14" + } }, - "node_modules/@tybys/wasm-util": { - "version": "0.10.1", - "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz", - "integrity": "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==", + "node_modules/@sentry/bundler-plugin-core": { + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/@sentry/bundler-plugin-core/-/bundler-plugin-core-4.9.1.tgz", + "integrity": "sha512-moii+w7N8k8WdvkX7qCDY9iRBlhgHlhTHTUQwF2FNMhBHuqlNpVcSJJqJMjFUQcjYMBDrZgxhfKV18bt5ixwlQ==", "dev": true, "license": "MIT", - "optional": true, "dependencies": { - "tslib": "^2.4.0" + "@babel/core": "^7.18.5", + "@sentry/babel-plugin-component-annotate": "4.9.1", + "@sentry/cli": "^2.57.0", + "dotenv": "^16.3.1", + "find-up": "^5.0.0", + "glob": "^10.5.0", + "magic-string": "0.30.8", + "unplugin": "1.0.1" + }, + "engines": { + "node": ">= 14" } }, - "node_modules/@types/chai": { - "version": "5.2.3", - "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz", - "integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==", + "node_modules/@sentry/cli": { + "version": "2.58.5", + "resolved": "https://registry.npmjs.org/@sentry/cli/-/cli-2.58.5.tgz", + "integrity": "sha512-tavJ7yGUZV+z3Ct2/ZB6mg339i08sAk6HDkgqmSRuQEu2iLS5sl9HIvuXfM6xjv8fwlgFOSy++WNABNAcGHUbg==", "dev": true, - "license": "MIT", + "hasInstallScript": true, + "license": "FSL-1.1-MIT", "dependencies": { - "@types/deep-eql": "*", - "assertion-error": "^2.0.1" + "https-proxy-agent": "^5.0.0", + "node-fetch": "^2.6.7", + "progress": "^2.0.3", + "proxy-from-env": "^1.1.0", + "which": "^2.0.2" + }, + "bin": { + "sentry-cli": "bin/sentry-cli" + }, + "engines": { + "node": ">= 10" + }, + "optionalDependencies": { + "@sentry/cli-darwin": "2.58.5", + "@sentry/cli-linux-arm": "2.58.5", + "@sentry/cli-linux-arm64": "2.58.5", + "@sentry/cli-linux-i686": "2.58.5", + "@sentry/cli-linux-x64": "2.58.5", + "@sentry/cli-win32-arm64": "2.58.5", + "@sentry/cli-win32-i686": "2.58.5", + "@sentry/cli-win32-x64": "2.58.5" + } + }, + "node_modules/@sentry/cli-darwin": { + "version": "2.58.5", + "resolved": "https://registry.npmjs.org/@sentry/cli-darwin/-/cli-darwin-2.58.5.tgz", + "integrity": "sha512-lYrNzenZFJftfwSya7gwrHGxtE+Kob/e1sr9lmHMFOd4utDlmq0XFDllmdZAMf21fxcPRI1GL28ejZ3bId01fQ==", + "dev": true, + "license": "FSL-1.1-MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=10" } }, - "node_modules/@types/deep-eql": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz", - "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==", + "node_modules/@sentry/cli-linux-arm": { + "version": "2.58.5", + "resolved": "https://registry.npmjs.org/@sentry/cli-linux-arm/-/cli-linux-arm-2.58.5.tgz", + "integrity": "sha512-KtHweSIomYL4WVDrBrYSYJricKAAzxUgX86kc6OnlikbyOhoK6Fy8Vs6vwd52P6dvWPjgrMpUYjW2M5pYXQDUw==", + "cpu": [ + "arm" + ], "dev": true, - "license": "MIT" + "license": "FSL-1.1-MIT", + "optional": true, + "os": [ + "linux", + "freebsd", + "android" + ], + "engines": { + "node": ">=10" + } }, - "node_modules/@types/estree": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", - "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "node_modules/@sentry/cli-linux-arm64": { + "version": "2.58.5", + "resolved": "https://registry.npmjs.org/@sentry/cli-linux-arm64/-/cli-linux-arm64-2.58.5.tgz", + "integrity": "sha512-/4gywFeBqRB6tR/iGMRAJ3HRqY6Z7Yp4l8ZCbl0TDLAfHNxu7schEw4tSnm2/Hh9eNMiOVy4z58uzAWlZXAYBQ==", + "cpu": [ + "arm64" + ], "dev": true, - "license": "MIT" + "license": "FSL-1.1-MIT", + "optional": true, + "os": [ + "linux", + "freebsd", + "android" + ], + "engines": { + "node": ">=10" + } }, - "node_modules/@types/node": { - "version": "25.5.2", - "resolved": "https://registry.npmjs.org/@types/node/-/node-25.5.2.tgz", - "integrity": "sha512-tO4ZIRKNC+MDWV4qKVZe3Ql/woTnmHDr5JD8UI5hn2pwBrHEwOEMZK7WlNb5RKB6EoJ02gwmQS9OrjuFnZYdpg==", + "node_modules/@sentry/cli-linux-i686": { + "version": "2.58.5", + "resolved": "https://registry.npmjs.org/@sentry/cli-linux-i686/-/cli-linux-i686-2.58.5.tgz", + "integrity": "sha512-G7261dkmyxqlMdyvyP06b+RTIVzp1gZNgglj5UksxSouSUqRd/46W/2pQeOMPhloDYo9yLtCN2YFb3Mw4aUsWw==", + "cpu": [ + "x86", + "ia32" + ], "dev": true, - "license": "MIT", - "dependencies": { - "undici-types": "~7.18.0" + "license": "FSL-1.1-MIT", + "optional": true, + "os": [ + "linux", + "freebsd", + "android" + ], + "engines": { + "node": ">=10" } }, - "node_modules/@vitest/expect": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.2.tgz", - "integrity": "sha512-gbu+7B0YgUJ2nkdsRJrFFW6X7NTP44WlhiclHniUhxADQJH5Szt9mZ9hWnJPJ8YwOK5zUOSSlSvyzRf0u1DSBQ==", + "node_modules/@sentry/cli-linux-x64": { + "version": "2.58.5", + "resolved": "https://registry.npmjs.org/@sentry/cli-linux-x64/-/cli-linux-x64-2.58.5.tgz", + "integrity": "sha512-rP04494RSmt86xChkQ+ecBNRYSPbyXc4u0IA7R7N1pSLCyO74e5w5Al+LnAq35cMfVbZgz5Sm0iGLjyiUu4I1g==", + "cpu": [ + "x64" + ], "dev": true, - "license": "MIT", - "dependencies": { - "@standard-schema/spec": "^1.1.0", - "@types/chai": "^5.2.2", - "@vitest/spy": "4.1.2", - "@vitest/utils": "4.1.2", - "chai": "^6.2.2", - "tinyrainbow": "^3.1.0" - }, - "funding": { - "url": "https://opencollective.com/vitest" + "license": "FSL-1.1-MIT", + "optional": true, + "os": [ + "linux", + "freebsd", + "android" + ], + "engines": { + "node": ">=10" } }, - "node_modules/@vitest/mocker": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.2.tgz", - "integrity": "sha512-Ize4iQtEALHDttPRCmN+FKqOl2vxTiNUhzobQFFt/BM1lRUTG7zRCLOykG/6Vo4E4hnUdfVLo5/eqKPukcWW7Q==", + "node_modules/@sentry/cli-win32-arm64": { + "version": "2.58.5", + "resolved": "https://registry.npmjs.org/@sentry/cli-win32-arm64/-/cli-win32-arm64-2.58.5.tgz", + "integrity": "sha512-AOJ2nCXlQL1KBaCzv38m3i2VmSHNurUpm7xVKd6yAHX+ZoVBI8VT0EgvwmtJR2TY2N2hNCC7UrgRmdUsQ152bA==", + "cpu": [ + "arm64" + ], "dev": true, - "license": "MIT", + "license": "FSL-1.1-MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@sentry/cli-win32-i686": { + "version": "2.58.5", + "resolved": "https://registry.npmjs.org/@sentry/cli-win32-i686/-/cli-win32-i686-2.58.5.tgz", + "integrity": "sha512-EsuboLSOnlrN7MMPJ1eFvfMDm+BnzOaSWl8eYhNo8W/BIrmNgpRUdBwnWn9Q2UOjJj5ZopukmsiMYtU/D7ml9g==", + "cpu": [ + "x86", + "ia32" + ], + "dev": true, + "license": "FSL-1.1-MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@sentry/cli-win32-x64": { + "version": "2.58.5", + "resolved": "https://registry.npmjs.org/@sentry/cli-win32-x64/-/cli-win32-x64-2.58.5.tgz", + "integrity": "sha512-IZf+XIMiQwj+5NzqbOQfywlOitmCV424Vtf9c+ep61AaVScUFD1TSrQbOcJJv5xGxhlxNOMNgMeZhdexdzrKZg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "FSL-1.1-MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@sentry/vite-plugin": { + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/@sentry/vite-plugin/-/vite-plugin-4.9.1.tgz", + "integrity": "sha512-Tlyg2cyFYp/icX58GWvfpvZr9NLdLs2/xyFVyS8pQ0faZWmoXic3FMzoXYHV1gsdMbL1Yy5WQvGJy8j1rS8LGA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sentry/bundler-plugin-core": "4.9.1", + "unplugin": "1.0.1" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/@standard-schema/spec": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", + "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tybys/wasm-util": { + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz", + "integrity": "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@types/chai": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz", + "integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/deep-eql": "*", + "assertion-error": "^2.0.1" + } + }, + "node_modules/@types/deep-eql": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz", + "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "25.6.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-25.6.0.tgz", + "integrity": "sha512-+qIYRKdNYJwY3vRCZMdJbPLJAtGjQBudzZzdzwQYkEPQd+PJGixUL5QfvCLDaULoLv+RhT3LDkwEfKaAkgSmNQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~7.19.0" + } + }, + "node_modules/@vitest/expect": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.5.tgz", + "integrity": "sha512-PWBaRY5JoKuRnHlUHfpV/KohFylaDZTupcXN1H9vYryNLOnitSw60Mw9IAE2r67NbwwzBw/Cc/8q9BK3kIX8Kw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@standard-schema/spec": "^1.1.0", + "@types/chai": "^5.2.2", + "@vitest/spy": "4.1.5", + "@vitest/utils": "4.1.5", + "chai": "^6.2.2", + "tinyrainbow": "^3.1.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/mocker": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.5.tgz", + "integrity": "sha512-/x2EmFC4mT4NNzqvC3fmesuV97w5FC903KPmey4gsnJiMQ3Be1IlDKVaDaG8iqaLFHqJ2FVEkxZk5VmeLjIItw==", + "dev": true, + "license": "MIT", "dependencies": { - "@vitest/spy": "4.1.2", + "@vitest/spy": "4.1.5", "estree-walker": "^3.0.3", "magic-string": "^0.30.21" }, @@ -664,10 +1204,20 @@ } } }, + "node_modules/@vitest/mocker/node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, "node_modules/@vitest/pretty-format": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.2.tgz", - "integrity": "sha512-dwQga8aejqeuB+TvXCMzSQemvV9hNEtDDpgUKDzOmNQayl2OG241PSWeJwKRH3CiC+sESrmoFd49rfnq7T4RnA==", + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.5.tgz", + "integrity": "sha512-7I3q6l5qr03dVfMX2wCo9FxwSJbPdwKjy2uu/YPpU3wfHvIL4QHwVRp57OfGrDFeUJ8/8QdfBKIV12FTtLn00g==", "dev": true, "license": "MIT", "dependencies": { @@ -678,13 +1228,13 @@ } }, "node_modules/@vitest/runner": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.2.tgz", - "integrity": "sha512-Gr+FQan34CdiYAwpGJmQG8PgkyFVmARK8/xSijia3eTFgVfpcpztWLuP6FttGNfPLJhaZVP/euvujeNYar36OQ==", + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.5.tgz", + "integrity": "sha512-2D+o7Pr82IEO46YPpoA/YU0neeyr6FTerQb5Ro7BUnBuv6NQtT/kmVnczngiMEBhzgqz2UZYl5gArejsyERDSQ==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/utils": "4.1.2", + "@vitest/utils": "4.1.5", "pathe": "^2.0.3" }, "funding": { @@ -692,14 +1242,14 @@ } }, "node_modules/@vitest/snapshot": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.2.tgz", - "integrity": "sha512-g7yfUmxYS4mNxk31qbOYsSt2F4m1E02LFqO53Xpzg3zKMhLAPZAjjfyl9e6z7HrW6LvUdTwAQR3HHfLjpko16A==", + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.5.tgz", + "integrity": "sha512-zypXEt4KH/XgKGPUz4eC2AvErYx0My5hfL8oDb1HzGFpEk1P62bxSohdyOmvz+d9UJwanI68MKwr2EquOaOgMQ==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/pretty-format": "4.1.2", - "@vitest/utils": "4.1.2", + "@vitest/pretty-format": "4.1.5", + "@vitest/utils": "4.1.5", "magic-string": "^0.30.21", "pathe": "^2.0.3" }, @@ -707,10 +1257,20 @@ "url": "https://opencollective.com/vitest" } }, + "node_modules/@vitest/snapshot/node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, "node_modules/@vitest/spy": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.2.tgz", - "integrity": "sha512-DU4fBnbVCJGNBwVA6xSToNXrkZNSiw59H8tcuUspVMsBDBST4nfvsPsEHDHGtWRRnqBERBQu7TrTKskmjqTXKA==", + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.5.tgz", + "integrity": "sha512-2lNOsh6+R2Idnf1TCZqSwYlKN2E/iDlD8sgU59kYVl+OMDmvldO1VDk39smRfpUNwYpNRVn3w4YfuC7KfbBnkQ==", "dev": true, "license": "MIT", "funding": { @@ -718,13 +1278,13 @@ } }, "node_modules/@vitest/utils": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.2.tgz", - "integrity": "sha512-xw2/TiX82lQHA06cgbqRKFb5lCAy3axQ4H4SoUFhUsg+wztiet+co86IAMDtF6Vm1hc7J6j09oh/rgDn+JdKIQ==", + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.5.tgz", + "integrity": "sha512-76wdkrmfXfqGjueGgnb45ITPyUi1ycZ4IHgC2bhPDUfWHklY/q3MdLOAB+TF1e6xfl8NxNY0ZYaPCFNWSsw3Ug==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/pretty-format": "4.1.2", + "@vitest/pretty-format": "4.1.5", "convert-source-map": "^2.0.0", "tinyrainbow": "^3.1.0" }, @@ -732,6 +1292,72 @@ "url": "https://opencollective.com/vitest" } }, + "node_modules/acorn": { + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", + "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "license": "ISC", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, "node_modules/assertion-error": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", @@ -742,6 +1368,26 @@ "node": ">=12" } }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/baseline-browser-mapping": { + "version": "2.10.21", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.21.tgz", + "integrity": "sha512-Q+rUQ7Uz8AHM7DEaNdwvfFCTq7a43lNTzuS94eiWqwyxfV/wJv+oUivef51T91mmRY4d4A1u9rcSvkeufCVXlA==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/bidi-js": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/bidi-js/-/bidi-js-1.0.3.tgz", @@ -752,6 +1398,97 @@ "require-from-string": "^2.0.2" } }, + "node_modules/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/brace-expansion": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.0.tgz", + "integrity": "sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browserslist": { + "version": "4.28.2", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.2.tgz", + "integrity": "sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "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" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001790", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001790.tgz", + "integrity": "sha512-bOoxfJPyYo+ds6W0YfptaCWbFnJYjh2Y1Eow5lRv+vI2u8ganPZqNm1JwNh0t2ELQCqIWg4B3dWEusgAmsoyOw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, "node_modules/chai": { "version": "6.2.2", "resolved": "https://registry.npmjs.org/chai/-/chai-6.2.2.tgz", @@ -762,6 +1499,51 @@ "node": ">=18" } }, + "node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, "node_modules/convert-source-map": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", @@ -769,6 +1551,21 @@ "dev": true, "license": "MIT" }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, "node_modules/css-tree": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-3.2.1.tgz", @@ -797,8 +1594,26 @@ "node": "^20.19.0 || ^22.12.0 || >=24.0.0" } }, - "node_modules/decimal.js": { - "version": "10.6.0", + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decimal.js": { + "version": "10.6.0", "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.6.0.tgz", "integrity": "sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==", "dev": true, @@ -814,14 +1629,48 @@ "node": ">=8" } }, + "node_modules/dotenv": { + "version": "16.6.1", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz", + "integrity": "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "dev": true, + "license": "MIT" + }, + "node_modules/electron-to-chromium": { + "version": "1.5.343", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.343.tgz", + "integrity": "sha512-YHnQ3MXI08icvL9ZKnEBy05F2EQ8ob01UaMOuMbM8l+4UcAq6MPPbBTJBbsBUg3H8JeZNt+O4fjsoWth3p6IFg==", + "dev": true, + "license": "ISC" + }, + "node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true, + "license": "MIT" + }, "node_modules/entities": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", - "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-8.0.0.tgz", + "integrity": "sha512-zwfzJecQ/Uej6tusMqwAqU/6KL2XaB2VZ2Jg54Je6ahNBGNH6Ek6g3jjNCF0fG9EWQKGZNddNjU5F1ZQn/sBnA==", "dev": true, "license": "BSD-2-Clause", "engines": { - "node": ">=0.12" + "node": ">=20.19.0" }, "funding": { "url": "https://github.com/fb55/entities?sponsor=1" @@ -834,6 +1683,16 @@ "dev": true, "license": "MIT" }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/estree-walker": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", @@ -854,22 +1713,51 @@ "node": ">=12.0.0" } }, - "node_modules/fdir": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", - "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", "dev": true, "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, "engines": { - "node": ">=12.0.0" + "node": ">=8" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" }, - "peerDependencies": { - "picomatch": "^3 || ^4" + "engines": { + "node": ">=10" }, - "peerDependenciesMeta": { - "picomatch": { - "optional": true - } + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/foreground-child": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", + "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", + "dev": true, + "license": "ISC", + "dependencies": { + "cross-spawn": "^7.0.6", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, "node_modules/fsevents": { @@ -887,6 +1775,51 @@ "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/glob": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", + "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "dev": true, + "license": "ISC", + "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" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/html-encoding-sniffer": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-6.0.0.tgz", @@ -900,6 +1833,76 @@ "node": "^20.19.0 || ^22.12.0 || >=24.0.0" } }, + "node_modules/https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "license": "MIT", + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, "node_modules/is-potential-custom-element-name": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", @@ -907,15 +1910,45 @@ "dev": true, "license": "MIT" }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true, + "license": "MIT" + }, "node_modules/jsdom": { - "version": "29.0.1", - "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-29.0.1.tgz", - "integrity": "sha512-z6JOK5gRO7aMybVq/y/MlIpKh8JIi68FBKMUtKkK2KH/wMSRlCxQ682d08LB9fYXplyY/UXG8P4XXTScmdjApg==", + "version": "29.0.2", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-29.0.2.tgz", + "integrity": "sha512-9VnGEBosc/ZpwyOsJBCQ/3I5p7Q5ngOY14a9bf5btenAORmZfDse1ZEheMiWcJ3h81+Fv7HmJFdS0szo/waF2w==", "dev": true, "license": "MIT", "dependencies": { - "@asamuzakjp/css-color": "^5.0.1", - "@asamuzakjp/dom-selector": "^7.0.3", + "@asamuzakjp/css-color": "^5.1.5", + "@asamuzakjp/dom-selector": "^7.0.6", "@bramus/specificity": "^2.4.2", "@csstools/css-syntax-patches-for-csstree": "^1.1.1", "@exodus/bytes": "^1.15.0", @@ -948,6 +1981,42 @@ } } }, + "node_modules/jsdom/node_modules/lru-cache": { + "version": "11.3.5", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.3.5.tgz", + "integrity": "sha512-NxVFwLAnrd9i7KUBxC4DrUhmgjzOs+1Qm50D3oF1/oL+r1NpZ4gA7xvG0/zJ8evR7zIKn4vLf7qTNduWFtCrRw==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/lightningcss": { "version": "1.32.0", "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz", @@ -1209,24 +2278,43 @@ "url": "https://opencollective.com/parcel" } }, - "node_modules/lru-cache": { - "version": "11.2.7", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.7.tgz", - "integrity": "sha512-aY/R+aEsRelme17KGQa/1ZSIpLpNYYrhcrepKTZgE+W3WM16YMCaPwOHLHsmopZHELU0Ojin1lPVxKR0MihncA==", + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", "dev": true, - "license": "BlueOak-1.0.0", + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, "engines": { - "node": "20 || >=22" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" } }, "node_modules/magic-string": { - "version": "0.30.21", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", - "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "version": "0.30.8", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.8.tgz", + "integrity": "sha512-ISQTe55T2ao7XtlAStud6qwYPZjE4GK1S/BeVPus4jrq6JuOnQ00YKQC581RWhR122W7msZV263KzVeLoqidyQ==", "dev": true, "license": "MIT", "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.5" + "@jridgewell/sourcemap-codec": "^1.4.15" + }, + "engines": { + "node": ">=12" } }, "node_modules/mdn-data": { @@ -1236,6 +2324,39 @@ "dev": true, "license": "CC0-1.0" }, + "node_modules/minimatch": { + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", + "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.2" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/minipass": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", + "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, "node_modules/nanoid": { "version": "3.3.11", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", @@ -1255,6 +2376,69 @@ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" } }, + "node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/node-fetch/node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "dev": true, + "license": "MIT" + }, + "node_modules/node-fetch/node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", + "dev": true, + "license": "BSD-2-Clause" + }, + "node_modules/node-fetch/node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "dev": true, + "license": "MIT", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, + "node_modules/node-releases": { + "version": "2.0.38", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.38.tgz", + "integrity": "sha512-3qT/88Y3FbH/Kx4szpQQ4HzUbVrHPKTLVpVocKiLfoYvw9XSGOX2FmD2d6DrXbVYyAQTF2HeF6My8jmzx7/CRw==", + "dev": true, + "license": "MIT" + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/obug": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/obug/-/obug-2.1.1.tgz", @@ -1267,24 +2451,107 @@ "license": "MIT" }, "node_modules/overlayscrollbars": { - "version": "2.14.0", - "resolved": "https://registry.npmjs.org/overlayscrollbars/-/overlayscrollbars-2.14.0.tgz", - "integrity": "sha512-RjV0pqc79kYhQLC3vTcLRb5GLpI1n6qh0Oua3g+bGH4EgNOJHVBGP7u0zZtxoAa0dkHlAqTTSYRb9MMmxNLjig==", + "version": "2.15.1", + "resolved": "https://registry.npmjs.org/overlayscrollbars/-/overlayscrollbars-2.15.1.tgz", + "integrity": "sha512-glX26JwjL+Tkzv0JNOWdW4VozP5dGXO+Wx8+TPrdTEJTSYT/8eJS8yXM+fewjU0nFq/JeCa+X+BqABNjC4YZSA==", "license": "MIT" }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/package-json-from-dist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", + "dev": true, + "license": "BlueOak-1.0.0" + }, "node_modules/parse5": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/parse5/-/parse5-8.0.0.tgz", - "integrity": "sha512-9m4m5GSgXjL4AjumKzq1Fgfp3Z8rsvjRNbnkVwfu2ImRqE5D0LnY2QfDen18FSY9C573YU5XxSapdHZTZ2WolA==", + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-8.0.1.tgz", + "integrity": "sha512-z1e/HMG90obSGeidlli3hj7cbocou0/wa5HacvI3ASx34PecNjNQeaHNo5WIZpWofN9kgkqV1q5YvXe3F0FoPw==", "dev": true, "license": "MIT", "dependencies": { - "entities": "^6.0.0" + "entities": "^8.0.0" }, "funding": { "url": "https://github.com/inikulin/parse5?sponsor=1" } }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/path-scurry/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true, + "license": "ISC" + }, "node_modules/pathe": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", @@ -1300,22 +2567,22 @@ "license": "ISC" }, "node_modules/picomatch": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", - "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", "dev": true, "license": "MIT", "engines": { - "node": ">=12" + "node": ">=8.6" }, "funding": { "url": "https://github.com/sponsors/jonschlinkert" } }, "node_modules/postcss": { - "version": "8.5.8", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.8.tgz", - "integrity": "sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg==", + "version": "8.5.10", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.10.tgz", + "integrity": "sha512-pMMHxBOZKFU6HgAZ4eyGnwXF/EvPGGqUr0MnZ5+99485wwW41kW91A4LOGxSHhgugZmSChL5AlElNdwlNgcnLQ==", "dev": true, "funding": [ { @@ -1341,104 +2608,284 @@ "node": "^10 || ^12 || >=14" } }, - "node_modules/punycode": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", - "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "node_modules/progress": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", + "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "dev": true, + "license": "MIT" + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/rolldown": { + "version": "1.0.0-rc.16", + "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.0-rc.16.tgz", + "integrity": "sha512-rzi5WqKzEZw3SooTt7cgm4eqIoujPIyGcJNGFL7iPEuajQw7vxMHUkXylu4/vhCkJGXsgRmxqMKXUpT6FEgl0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@oxc-project/types": "=0.126.0", + "@rolldown/pluginutils": "1.0.0-rc.16" + }, + "bin": { + "rolldown": "bin/cli.mjs" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "optionalDependencies": { + "@rolldown/binding-android-arm64": "1.0.0-rc.16", + "@rolldown/binding-darwin-arm64": "1.0.0-rc.16", + "@rolldown/binding-darwin-x64": "1.0.0-rc.16", + "@rolldown/binding-freebsd-x64": "1.0.0-rc.16", + "@rolldown/binding-linux-arm-gnueabihf": "1.0.0-rc.16", + "@rolldown/binding-linux-arm64-gnu": "1.0.0-rc.16", + "@rolldown/binding-linux-arm64-musl": "1.0.0-rc.16", + "@rolldown/binding-linux-ppc64-gnu": "1.0.0-rc.16", + "@rolldown/binding-linux-s390x-gnu": "1.0.0-rc.16", + "@rolldown/binding-linux-x64-gnu": "1.0.0-rc.16", + "@rolldown/binding-linux-x64-musl": "1.0.0-rc.16", + "@rolldown/binding-openharmony-arm64": "1.0.0-rc.16", + "@rolldown/binding-wasm32-wasi": "1.0.0-rc.16", + "@rolldown/binding-win32-arm64-msvc": "1.0.0-rc.16", + "@rolldown/binding-win32-x64-msvc": "1.0.0-rc.16" + } + }, + "node_modules/saxes": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz", + "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==", + "dev": true, + "license": "ISC", + "dependencies": { + "xmlchars": "^2.2.0" + }, + "engines": { + "node": ">=v12.22.7" + } + }, + "node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/siginfo": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", + "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", + "dev": true, + "license": "ISC" + }, + "node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/stackback": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", + "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", + "dev": true, + "license": "MIT" + }, + "node_modules/std-env": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-4.1.0.tgz", + "integrity": "sha512-Rq7ybcX2RuC55r9oaPVEW7/xu3tj8u4GeBYHBWCychFtzMIr86A7e3PPEBPT37sHStKX3+TiX/Fr/ACmJLVlLQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", "dev": true, "license": "MIT", "engines": { - "node": ">=6" + "node": ">=8" } }, - "node_modules/require-from-string": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", - "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "node_modules/string-width-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/string-width-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "dev": true, "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, "engines": { - "node": ">=0.10.0" + "node": ">=8" } }, - "node_modules/rolldown": { - "version": "1.0.0-rc.12", - "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.0-rc.12.tgz", - "integrity": "sha512-yP4USLIMYrwpPHEFB5JGH1uxhcslv6/hL0OyvTuY+3qlOSJvZ7ntYnoWpehBxufkgN0cvXxppuTu5hHa/zPh+A==", + "node_modules/strip-ansi": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", "dev": true, "license": "MIT", "dependencies": { - "@oxc-project/types": "=0.122.0", - "@rolldown/pluginutils": "1.0.0-rc.12" - }, - "bin": { - "rolldown": "bin/cli.mjs" + "ansi-regex": "^6.2.2" }, "engines": { - "node": "^20.19.0 || >=22.12.0" + "node": ">=12" }, - "optionalDependencies": { - "@rolldown/binding-android-arm64": "1.0.0-rc.12", - "@rolldown/binding-darwin-arm64": "1.0.0-rc.12", - "@rolldown/binding-darwin-x64": "1.0.0-rc.12", - "@rolldown/binding-freebsd-x64": "1.0.0-rc.12", - "@rolldown/binding-linux-arm-gnueabihf": "1.0.0-rc.12", - "@rolldown/binding-linux-arm64-gnu": "1.0.0-rc.12", - "@rolldown/binding-linux-arm64-musl": "1.0.0-rc.12", - "@rolldown/binding-linux-ppc64-gnu": "1.0.0-rc.12", - "@rolldown/binding-linux-s390x-gnu": "1.0.0-rc.12", - "@rolldown/binding-linux-x64-gnu": "1.0.0-rc.12", - "@rolldown/binding-linux-x64-musl": "1.0.0-rc.12", - "@rolldown/binding-openharmony-arm64": "1.0.0-rc.12", - "@rolldown/binding-wasm32-wasi": "1.0.0-rc.12", - "@rolldown/binding-win32-arm64-msvc": "1.0.0-rc.12", - "@rolldown/binding-win32-x64-msvc": "1.0.0-rc.12" + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" } }, - "node_modules/saxes": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz", - "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==", + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "dev": true, - "license": "ISC", + "license": "MIT", "dependencies": { - "xmlchars": "^2.2.0" + "ansi-regex": "^5.0.1" }, "engines": { - "node": ">=v12.22.7" + "node": ">=8" } }, - "node_modules/siginfo": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", - "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", - "dev": true, - "license": "ISC" - }, - "node_modules/source-map-js": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", - "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "node_modules/strip-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", "dev": true, - "license": "BSD-3-Clause", + "license": "MIT", "engines": { - "node": ">=0.10.0" + "node": ">=8" } }, - "node_modules/stackback": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", - "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", - "dev": true, - "license": "MIT" - }, - "node_modules/std-env": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/std-env/-/std-env-4.0.0.tgz", - "integrity": "sha512-zUMPtQ/HBY3/50VbpkupYHbRroTRZJPRLvreamgErJVys0ceuzMkD44J/QjqhHjOzK42GQ3QZIeFG1OYfOtKqQ==", - "dev": true, - "license": "MIT" - }, "node_modules/symbol-tree": { "version": "3.2.4", "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", @@ -1454,9 +2901,9 @@ "license": "MIT" }, "node_modules/tinyexec": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.0.4.tgz", - "integrity": "sha512-u9r3uZC0bdpGOXtlxUIdwf9pkmvhqJdrVCH9fapQtgy/OeTTMZ1nqH7agtvEfmGui6e1XxjcdrlxvxJvc3sMqw==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.1.1.tgz", + "integrity": "sha512-VKS/ZaQhhkKFMANmAOhhXVoIfBXblQxGX1myCQ2faQrfmobMftXeJPcZGp0gS07ocvGJWDLZGyOZDadDBqYIJg==", "dev": true, "license": "MIT", "engines": { @@ -1464,14 +2911,14 @@ } }, "node_modules/tinyglobby": { - "version": "0.2.15", - "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", - "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", + "version": "0.2.16", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.16.tgz", + "integrity": "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==", "dev": true, "license": "MIT", "dependencies": { "fdir": "^6.5.0", - "picomatch": "^4.0.3" + "picomatch": "^4.0.4" }, "engines": { "node": ">=12.0.0" @@ -1480,6 +2927,37 @@ "url": "https://github.com/sponsors/SuperchupuDev" } }, + "node_modules/tinyglobby/node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/tinyglobby/node_modules/picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/tinyrainbow": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.1.0.tgz", @@ -1491,25 +2969,38 @@ } }, "node_modules/tldts": { - "version": "7.0.26", - "resolved": "https://registry.npmjs.org/tldts/-/tldts-7.0.26.tgz", - "integrity": "sha512-WiGwQjr0qYdNNG8KpMKlSvpxz652lqa3Rd+/hSaDcY4Uo6SKWZq2LAF+hsAhUewTtYhXlorBKgNF3Kk8hnjGoQ==", + "version": "7.0.28", + "resolved": "https://registry.npmjs.org/tldts/-/tldts-7.0.28.tgz", + "integrity": "sha512-+Zg3vWhRUv8B1maGSTFdev9mjoo8Etn2Ayfs4cnjlD3CsGkxXX4QyW3j2WJ0wdjYcYmy7Lx2RDsZMhgCWafKIw==", "dev": true, "license": "MIT", "dependencies": { - "tldts-core": "^7.0.26" + "tldts-core": "^7.0.28" }, "bin": { "tldts": "bin/cli.js" } }, "node_modules/tldts-core": { - "version": "7.0.26", - "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-7.0.26.tgz", - "integrity": "sha512-5WJ2SqFsv4G2Dwi7ZFVRnz6b2H1od39QME1lc2y5Ew3eWiZMAeqOAfWpRP9jHvhUl881406QtZTODvjttJs+ew==", + "version": "7.0.28", + "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-7.0.28.tgz", + "integrity": "sha512-7W5Efjhsc3chVdFhqtaU0KtK32J37Zcr9RKtID54nG+tIpcY79CQK/veYPODxtD/LJ4Lue66jvrQzIX2Z2/pUQ==", "dev": true, "license": "MIT" }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, "node_modules/tough-cookie": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-6.0.1.tgz", @@ -1545,9 +3036,9 @@ "optional": true }, "node_modules/typescript": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.2.tgz", - "integrity": "sha512-bGdAIrZ0wiGDo5l8c++HWtbaNCWTS4UTv7RaTH/ThVIgjkveJt83m74bBHMJkuCbslY8ixgLBVZJIOiQlQTjfQ==", + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.3.tgz", + "integrity": "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==", "dev": true, "license": "Apache-2.0", "bin": { @@ -1559,9 +3050,9 @@ } }, "node_modules/undici": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/undici/-/undici-7.24.5.tgz", - "integrity": "sha512-3IWdCpjgxp15CbJnsi/Y9TCDE7HWVN19j1hmzVhoAkY/+CJx449tVxT5wZc1Gwg8J+P0LWvzlBzxYRnHJ+1i7Q==", + "version": "7.25.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-7.25.0.tgz", + "integrity": "sha512-xXnp4kTyor2Zq+J1FfPI6Eq3ew5h6Vl0F/8d9XU5zZQf1tX9s2Su1/3PiMmUANFULpmksxkClamIZcaUqryHsQ==", "dev": true, "license": "MIT", "engines": { @@ -1569,24 +3060,68 @@ } }, "node_modules/undici-types": { - "version": "7.18.2", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz", - "integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==", + "version": "7.19.2", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.19.2.tgz", + "integrity": "sha512-qYVnV5OEm2AW8cJMCpdV20CDyaN3g0AjDlOGf1OW4iaDEx8MwdtChUp4zu4H0VP3nDRF/8RKWH+IPp9uW0YGZg==", "dev": true, "license": "MIT" }, + "node_modules/unplugin": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/unplugin/-/unplugin-1.0.1.tgz", + "integrity": "sha512-aqrHaVBWW1JVKBHmGo33T5TxeL0qWzfvjWokObHA9bYmN7eNDkwOxmLjhioHl9878qDFMAaT51XNroRyuz7WxA==", + "dev": true, + "license": "MIT", + "dependencies": { + "acorn": "^8.8.1", + "chokidar": "^3.5.3", + "webpack-sources": "^3.2.3", + "webpack-virtual-modules": "^0.5.0" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", + "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, "node_modules/vite": { - "version": "8.0.5", - "resolved": "https://registry.npmjs.org/vite/-/vite-8.0.5.tgz", - "integrity": "sha512-nmu43Qvq9UopTRfMx2jOYW5l16pb3iDC1JH6yMuPkpVbzK0k+L7dfsEDH4jRgYFmsg0sTAqkojoZgzLMlwHsCQ==", + "version": "8.0.9", + "resolved": "https://registry.npmjs.org/vite/-/vite-8.0.9.tgz", + "integrity": "sha512-t7g7GVRpMXjNpa67HaVWI/8BWtdVIQPCL2WoozXXA7LBGEFK4AkkKkHx2hAQf5x1GZSlcmEDPkVLSGahxnEEZw==", "dev": true, "license": "MIT", "dependencies": { "lightningcss": "^1.32.0", "picomatch": "^4.0.4", - "postcss": "^8.5.8", - "rolldown": "1.0.0-rc.12", - "tinyglobby": "^0.2.15" + "postcss": "^8.5.10", + "rolldown": "1.0.0-rc.16", + "tinyglobby": "^0.2.16" }, "bin": { "vite": "bin/vite.js" @@ -1653,20 +3188,33 @@ } } }, + "node_modules/vite/node_modules/picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/vitest": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.2.tgz", - "integrity": "sha512-xjR1dMTVHlFLh98JE3i/f/WePqJsah4A0FK9cc8Ehp9Udk0AZk6ccpIZhh1qJ/yxVWRZ+Q54ocnD8TXmkhspGg==", + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.5.tgz", + "integrity": "sha512-9Xx1v3/ih3m9hN+SbfkUyy0JAs72ap3r7joc87XL6jwF0jGg6mFBvQ1SrwaX+h8BlkX6Hz9shdd1uo6AF+ZGpg==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/expect": "4.1.2", - "@vitest/mocker": "4.1.2", - "@vitest/pretty-format": "4.1.2", - "@vitest/runner": "4.1.2", - "@vitest/snapshot": "4.1.2", - "@vitest/spy": "4.1.2", - "@vitest/utils": "4.1.2", + "@vitest/expect": "4.1.5", + "@vitest/mocker": "4.1.5", + "@vitest/pretty-format": "4.1.5", + "@vitest/runner": "4.1.5", + "@vitest/snapshot": "4.1.5", + "@vitest/spy": "4.1.5", + "@vitest/utils": "4.1.5", "es-module-lexer": "^2.0.0", "expect-type": "^1.3.0", "magic-string": "^0.30.21", @@ -1694,10 +3242,12 @@ "@edge-runtime/vm": "*", "@opentelemetry/api": "^1.9.0", "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", - "@vitest/browser-playwright": "4.1.2", - "@vitest/browser-preview": "4.1.2", - "@vitest/browser-webdriverio": "4.1.2", - "@vitest/ui": "4.1.2", + "@vitest/browser-playwright": "4.1.5", + "@vitest/browser-preview": "4.1.5", + "@vitest/browser-webdriverio": "4.1.5", + "@vitest/coverage-istanbul": "4.1.5", + "@vitest/coverage-v8": "4.1.5", + "@vitest/ui": "4.1.5", "happy-dom": "*", "jsdom": "*", "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" @@ -1721,6 +3271,12 @@ "@vitest/browser-webdriverio": { "optional": true }, + "@vitest/coverage-istanbul": { + "optional": true + }, + "@vitest/coverage-v8": { + "optional": true + }, "@vitest/ui": { "optional": true }, @@ -1735,6 +3291,29 @@ } } }, + "node_modules/vitest/node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/vitest/node_modules/picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/w3c-xmlserializer": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz", @@ -1758,6 +3337,23 @@ "node": ">=20" } }, + "node_modules/webpack-sources": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.3.4.tgz", + "integrity": "sha512-7tP1PdV4vF+lYPnkMR0jMY5/la2ub5Fc/8VQrrU+lXkiM6C4TjVfGw7iKfyhnTQOsD+6Q/iKw0eFciziRgD58Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/webpack-virtual-modules": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/webpack-virtual-modules/-/webpack-virtual-modules-0.5.0.tgz", + "integrity": "sha512-kyDivFZ7ZM0BVOUteVbDFhlRt7Ah/CSPwJdi8hBpkK7QLumUqdLtVfm/PX/hkcnrvr0i77fO5+TjZ94Pe+C9iw==", + "dev": true, + "license": "MIT" + }, "node_modules/whatwg-mimetype": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-5.0.0.tgz", @@ -1783,6 +3379,22 @@ "node": "^20.19.0 || ^22.12.0 || >=24.0.0" } }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, "node_modules/why-is-node-running": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", @@ -1800,6 +3412,104 @@ "node": ">=8" } }, + "node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/wrap-ansi-cjs/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/xml-name-validator": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-5.0.0.tgz", @@ -1816,6 +3526,26 @@ "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", "dev": true, "license": "MIT" + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, + "license": "ISC" + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } } } } diff --git a/Frontend/package.json b/Frontend/package.json index 8a7aa0b8..befb08ce 100644 --- a/Frontend/package.json +++ b/Frontend/package.json @@ -6,7 +6,7 @@ "author": "Jordon Brooks", "license": "ISC", "dependencies": { - "overlayscrollbars": "^2.14.0" + "overlayscrollbars": "^2.15.1" }, "scripts": { "dev": "vite", @@ -16,10 +16,11 @@ "preview": "vite preview --strictPort --port 1420" }, "devDependencies": { - "@types/node": "^25.5.2", - "jsdom": "^29.0.1", - "typescript": "^6.0.2", - "vite": "^8.0.5", - "vitest": "^4.1.2" + "@sentry/vite-plugin": "^4.5.0", + "@types/node": "^25.6.0", + "jsdom": "^29.0.2", + "typescript": "^6.0.3", + "vite": "^8.0.9", + "vitest": "^4.1.4" } } diff --git a/Frontend/src/modals/confirm.html b/Frontend/src/modals/confirm.html new file mode 100644 index 00000000..f7ecc614 --- /dev/null +++ b/Frontend/src/modals/confirm.html @@ -0,0 +1,22 @@ + + diff --git a/Frontend/src/modals/settings.html b/Frontend/src/modals/settings.html index a6b0162d..d8eb8857 100644 --- a/Frontend/src/modals/settings.html +++ b/Frontend/src/modals/settings.html @@ -89,6 +89,14 @@

Settings

+
+ +
+ diff --git a/Frontend/src/scripts/features/confirmModal.ts b/Frontend/src/scripts/features/confirmModal.ts new file mode 100644 index 00000000..e3d42971 --- /dev/null +++ b/Frontend/src/scripts/features/confirmModal.ts @@ -0,0 +1,86 @@ +// Copyright © 2025-2026 OpenVCS Contributors +// SPDX-License-Identifier: GPL-3.0-or-later + +import { closeModal, hydrate, openModal } from '../ui/modals'; + +/** Options that control the shared confirmation modal content. */ +export interface ConfirmModalOptions { + title?: string; + message: string; + hint?: string; + confirmLabel?: string; + cancelLabel?: string; + danger?: boolean; +} + +let pendingResolve: ((ok: boolean) => void) | null = null; + +/** Returns the shared confirmation modal element if it exists. */ +function getModal(): HTMLElement | null { + return document.getElementById('confirm-modal'); +} + +/** Resolves and clears any pending confirmation promise. */ +function resolvePending(ok: boolean) { + if (!pendingResolve) return; + const resolve = pendingResolve; + pendingResolve = null; + resolve(ok); +} + +/** Wires the shared confirmation modal once. */ +export function wireConfirmModal() { + const modal = getModal(); + if (!modal || (modal as HTMLElement & { __wired?: boolean }).__wired) return; + (modal as HTMLElement & { __wired?: boolean }).__wired = true; + + const titleEl = modal.querySelector('#confirm-modal-title'); + const hintEl = modal.querySelector('#confirm-modal-hint'); + const messageEl = modal.querySelector('#confirm-modal-message'); + const cancelBtn = modal.querySelector('#confirm-modal-cancel-btn'); + const confirmBtn = modal.querySelector('#confirm-modal-confirm-btn'); + + modal.addEventListener('modal:closed', () => { + resolvePending(false); + }); + + confirmBtn?.addEventListener('click', () => { + resolvePending(true); + closeModal('confirm-modal'); + }); + + (modal as HTMLElement & { setContent?: (opts: ConfirmModalOptions) => void }).setContent = (opts) => { + const title = String(opts.title || 'Confirm action').trim() || 'Confirm action'; + const hint = String(opts.hint || 'This cannot be undone.').trim() || 'This cannot be undone.'; + const confirmLabel = String(opts.confirmLabel || 'Confirm').trim() || 'Confirm'; + const cancelLabel = String(opts.cancelLabel || 'Cancel').trim() || 'Cancel'; + const isDanger = !!opts.danger; + + if (titleEl) titleEl.textContent = title; + if (hintEl) hintEl.textContent = hint; + if (messageEl) messageEl.textContent = opts.message; + if (cancelBtn) cancelBtn.textContent = cancelLabel; + if (confirmBtn) { + confirmBtn.textContent = confirmLabel; + confirmBtn.classList.toggle('danger', isDanger); + confirmBtn.classList.toggle('primary', !isDanger); + } + window.setTimeout(() => cancelBtn?.focus(), 0); + }; +} + +/** Opens the shared confirmation modal and resolves with the user's choice. */ +export function confirmWithModal(opts: ConfirmModalOptions): Promise { + hydrate('confirm-modal'); + wireConfirmModal(); + + const modal = getModal() as (HTMLElement & { setContent?: (opts: ConfirmModalOptions) => void }) | null; + modal?.setContent?.(opts); + + return new Promise((resolve) => { + const previousResolve = pendingResolve; + pendingResolve = resolve; + previousResolve?.(false); + openModal('confirm-modal'); + }); +} diff --git a/Frontend/src/scripts/features/repo/hydrate.ts b/Frontend/src/scripts/features/repo/hydrate.ts index 096baca1..7167c8a2 100644 --- a/Frontend/src/scripts/features/repo/hydrate.ts +++ b/Frontend/src/scripts/features/repo/hydrate.ts @@ -5,6 +5,24 @@ import { state, prefs } from '../../state/state'; import { renderList } from './list'; import { autoOpenFirstConflict } from '../conflicts'; +/** + * Yields control long enough for the browser to paint pending UI updates. + * + * This keeps the webview responsive before expensive repo refresh work starts. + * + * @returns A promise that resolves on the next paint opportunity. + */ +export function yieldToPaint(): Promise { + return new Promise((resolve) => { + if (document.visibilityState === 'visible') { + window.requestAnimationFrame(() => resolve()); + return; + } + + window.setTimeout(() => resolve(), 0); + }); +} + function normalizeFiles(files: any[]): any[] { return [...files].sort((a, b) => String(a?.path || '').localeCompare(String(b?.path || ''))); } @@ -35,9 +53,22 @@ function buildStatusSignature(input: { let lastStatusSignature = ''; +/** Returns a richer log message for common repository hydration failures. */ +function describeHydrationFailure(operation: string, error: unknown): string { + const message = String(error || '').trim(); + if (message === 'No repository selected') { + return `${operation} skipped: no repository selected; check whether a VCS backend is available and whether a repository was reopened successfully`; + } + if (message.includes('no longer available')) { + return `${operation} failed: active backend is no longer available; reopen the repository or re-enable the backend plugin`; + } + return `${operation} failed ${message}`.trim(); +} + export async function hydrateBranches(): Promise { if (!TAURI.has) return false; try { + await yieldToPaint(); const list = await TAURI.invoke('git_list_branches'); const head = await TAURI.invoke<{ detached: boolean; branch?: string; commit?: string }>('git_head_status').catch(() => ({ detached: false } as any)); const has = Array.isArray(list) && list.length > 0; @@ -55,7 +86,7 @@ export async function hydrateBranches(): Promise { } return false; } catch (e) { - console.warn('hydrateBranches failed', e); + console.warn(describeHydrationFailure('hydrateBranches', e), e); return false; } } @@ -63,6 +94,7 @@ export async function hydrateBranches(): Promise { export async function hydrateStatus() { if (!TAURI.has) return; try { + await yieldToPaint(); const result = await TAURI.invoke<{ files: any[]; ahead?: number; behind?: number }>('git_status'); const nextFiles = Array.isArray(result?.files) ? (result.files as any) : []; let nextMergeInProgress = false; @@ -115,7 +147,7 @@ export async function hydrateStatus() { void autoOpenFirstConflict(state.files as any); window.dispatchEvent(new CustomEvent('app:status-updated')); } catch (e) { - console.warn('hydrateStatus failed', e); + console.warn(describeHydrationFailure('hydrateStatus', e), e); state.files = []; state.mergeInProgress = false; state.seenConflicts = new Set(); @@ -130,6 +162,7 @@ export async function hydrateStatus() { export async function hydrateCommits() { if (!TAURI.has) return; try { + await yieldToPaint(); const list = await TAURI.invoke('git_log', { limit: 100 }); state.hasRepo = true; const baseCommits = Array.isArray(list) ? (list as any) : []; @@ -181,7 +214,7 @@ export async function hydrateCommits() { } if (prefs.tab === 'history') renderList(); } catch (e) { - console.warn('hydrateCommits failed', e); + console.warn(describeHydrationFailure('hydrateCommits', e), e); state.commits = []; } } @@ -189,11 +222,12 @@ export async function hydrateCommits() { export async function hydrateStash() { if (!TAURI.has) return; try { + await yieldToPaint(); const list = await TAURI.invoke('git_stash_list'); (state as any).stash = Array.isArray(list) ? (list as any) : []; if (prefs.tab === 'stash') renderList(); } catch (e) { - console.warn('hydrateStash failed', e); + console.warn(describeHydrationFailure('hydrateStash', e), e); (state as any).stash = []; } } diff --git a/Frontend/src/scripts/features/repo/index.ts b/Frontend/src/scripts/features/repo/index.ts index 38391e5a..a65a07f1 100644 --- a/Frontend/src/scripts/features/repo/index.ts +++ b/Frontend/src/scripts/features/repo/index.ts @@ -3,4 +3,4 @@ export { bindRepoHotkeys } from './hotkeys'; export { bindFilter } from './filter'; export { renderList, wireRenderListCallbacks } from './list'; -export { hydrateBranches, hydrateStatus, hydrateCommits, hydrateStash } from './hydrate'; +export { hydrateBranches, hydrateStatus, hydrateCommits, hydrateStash, yieldToPaint } from './hydrate'; diff --git a/Frontend/src/scripts/features/settings.ts b/Frontend/src/scripts/features/settings.ts index 40e4d976..71295b13 100644 --- a/Frontend/src/scripts/features/settings.ts +++ b/Frontend/src/scripts/features/settings.ts @@ -1,11 +1,13 @@ // Copyright © 2025-2026 OpenVCS Contributors // SPDX-License-Identifier: GPL-3.0-or-later import { TAURI } from '../lib/tauri'; +import { syncFrontendMonitoring } from '../lib/monitoring'; import { openModal, closeModal } from '../ui/modals'; import { toKebab } from '../lib/dom'; import { confirmBool } from '../lib/confirm'; import { notify } from '../lib/notify'; import { setTheme } from '../ui/layout'; +import { collectGeneralSettings, loadGeneralSettingsIntoForm } from './settingsGeneral'; import { DEFAULT_DARK_THEME_ID, DEFAULT_LIGHT_THEME_ID, DEFAULT_THEME_ID, getActiveThemeId, getAvailableThemes, refreshAvailableThemes, selectThemePack } from '../themes'; import { invokePluginAction, reloadPlugins } from '../plugins'; import type { PluginSummary } from '../plugins'; @@ -19,6 +21,7 @@ interface PluginMenuPayload { plugin_id: string; id: string; label: string; + surface: 'menubar' | 'settings'; elements: Array<{ type: 'text' | 'button' | string; id?: string; @@ -298,6 +301,10 @@ async function renderPluginMenus(modal: HTMLElement): Promise { }; for (const menu of menus) { + // Only render settings-surface menus in the settings modal. + const surface = String(menu.surface || 'menubar').toLowerCase(); + if (surface !== 'settings') continue; + const section = pluginSectionId(menu.plugin_id, menu.id); const navLi = document.createElement('li'); navLi.dataset.pluginMenu = 'true'; @@ -690,6 +697,7 @@ export function wireSettings() { if (TAURI.has) { await TAURI.invoke('set_global_settings', { cfg: next }); + await syncFrontendMonitoring(next); } modal.dataset.currentCfg = JSON.stringify(next); @@ -752,7 +760,7 @@ export function wireSettings() { reopen_last_repos: true, checks_on_launch: true, telemetry: false, - crash_reports: false, + crash_reports: true, }; cur.diff = { tab_width: 4, ignore_whitespace: 'none', max_file_size_mb: 10, intraline: true, show_binary_placeholders: true, external_diff: {enabled:false,path:'',args:''}, external_merge: {enabled:false,path:'',args:''}, binary_exts: ['png','jpg','dds','uasset'] }; cur.lfs = { enabled: true, concurrency: 4, require_lock_before_edit: false, background_fetch_on_checkout: true }; @@ -762,6 +770,7 @@ export function wireSettings() { cur.plugins = { disabled: [], enabled: [] }; await TAURI.invoke('set_global_settings', { cfg: cur }); + await syncFrontendMonitoring(cur); applyAnimationPreference(cur.performance?.animations); await loadSettingsIntoForm(modal); setTheme('system'); @@ -780,20 +789,7 @@ function collectSettingsFromForm(root: HTMLElement): GlobalSettings { const o: GlobalSettings = { ...base }; - const autoTheme = !!get('#set-theme-auto')?.checked; - const themePack = get('#set-theme')?.value || DEFAULT_LIGHT_THEME_ID; - const theme = autoTheme ? 'system' : modeForTheme(themePack); - - o.general = { - ...o.general, - theme, - theme_pack: themePack || DEFAULT_LIGHT_THEME_ID, - language: get('#set-language')?.value, - default_backend: (get('#set-default-backend')?.value || 'git') as any, - update_channel: get('#set-update-channel')?.value || 'stable', - reopen_last_repos: !!get('#set-reopen-last')?.checked, - checks_on_launch: !!get('#set-checks-on-launch')?.checked, - }; + o.general = collectGeneralSettings(root, o, modeForTheme); o.diff = { ...o.diff, @@ -903,35 +899,8 @@ export async function loadSettingsIntoForm(root?: HTMLElement) { await loadPluginsIntoForm(m, cfg); - const themeSel = get('#set-theme'); - const elAuto = get('#set-theme-auto'); - const themePref = (cfg.general?.theme || 'system') as 'system'|'light'|'dark'; - - if (elAuto) elAuto.checked = themePref === 'system'; - - if (themeSel) { - let desiredId = String(cfg.general?.theme_pack || DEFAULT_LIGHT_THEME_ID); - if (desiredId.trim().toLowerCase() === DEFAULT_THEME_ID) { - desiredId = themePref === 'dark' ? DEFAULT_DARK_THEME_ID : DEFAULT_LIGHT_THEME_ID; - } - await rebuildThemePackOptions(themeSel, { - desiredId, - forceReload: true, - }); - themeSel.disabled = themePref === 'system'; - if (themePref === 'system') { - themeSel.value = getActiveThemeId() || themeSel.value; - } - } - - const elLang = get('#set-language'); if (elLang) elLang.value = toKebab(cfg.general?.language); - await refreshDefaultBackendOptions(m, cfg); - const elChan = get('#set-update-channel'); if (elChan) { - elChan.value = toKebab(cfg.general?.update_channel); - } - const elReo = get('#set-reopen-last'); if (elReo) elReo.checked = !!cfg.general?.reopen_last_repos; - const elChk = get('#set-checks-on-launch'); if (elChk) elChk.checked = !!cfg.general?.checks_on_launch; - const elRl = get('#set-recents-limit'); if (elRl) elRl.value = String(cfg.ux?.recents_limit ?? 10); + await loadGeneralSettingsIntoForm(m, cfg, toKebab, refreshDefaultBackendOptions, rebuildThemePackOptions); + const elRl = get('#set-recents-limit'); if (elRl) elRl.value = String(cfg.ux?.recents_limit ?? 10); const elTw = get('#set-tab-width'); if (elTw) elTw.value = String(cfg.diff?.tab_width ?? 0); const elIw = get('#set-ignore-whitespace'); if (elIw) elIw.value = toKebab(cfg.diff?.ignore_whitespace); @@ -996,7 +965,12 @@ async function refreshDefaultBackendOptions(modal: HTMLElement, cfg: GlobalSetti } el.disabled = backends.length === 0; - if (!backends.length) return; + if (!backends.length) { + console.warn( + 'settings: no VCS backends are currently available; default backend selection is disabled', + ); + return; + } if (desired && backends.some(([id]) => id === desired)) { el.value = desired; } else { diff --git a/Frontend/src/scripts/features/settingsGeneral.test.ts b/Frontend/src/scripts/features/settingsGeneral.test.ts new file mode 100644 index 00000000..1199f499 --- /dev/null +++ b/Frontend/src/scripts/features/settingsGeneral.test.ts @@ -0,0 +1,55 @@ +// Copyright © 2025-2026 OpenVCS Contributors +// SPDX-License-Identifier: GPL-3.0-or-later + +import { describe, expect, it, vi } from 'vitest'; + +import { collectGeneralSettings, loadGeneralSettingsIntoForm } from './settingsGeneral'; + +describe('collectGeneralSettings', () => { + it('captures the crash report toggle from the general settings panel', () => { + document.body.innerHTML = ` +
+ + + + + + + + +
+ `; + + const root = document.body.firstElementChild as HTMLElement; + const general = collectGeneralSettings(root, {}, () => 'dark'); + expect(general?.crash_reports).toBe(true); + expect(general?.theme).toBe('system'); + }); +}); + +describe('loadGeneralSettingsIntoForm', () => { + it('loads the crash report toggle into the general settings panel', async () => { + document.body.innerHTML = ` +
+ + + + + +
+ `; + + const root = document.body.firstElementChild as HTMLElement; + const refreshDefaultBackendOptions = vi.fn().mockResolvedValue(undefined); + await loadGeneralSettingsIntoForm( + root, + { general: { language: 'system', update_channel: 'stable', reopen_last_repos: true, checks_on_launch: true, crash_reports: true } }, + (value) => String(value ?? ''), + refreshDefaultBackendOptions, + vi.fn().mockResolvedValue(undefined), + ); + + expect(refreshDefaultBackendOptions).toHaveBeenCalledWith(root, expect.any(Object)); + expect((root.querySelector('#set-crash-reports') as HTMLInputElement).checked).toBe(true); + }); +}); diff --git a/Frontend/src/scripts/features/settingsGeneral.ts b/Frontend/src/scripts/features/settingsGeneral.ts new file mode 100644 index 00000000..ec288a53 --- /dev/null +++ b/Frontend/src/scripts/features/settingsGeneral.ts @@ -0,0 +1,76 @@ +// Copyright © 2025-2026 OpenVCS Contributors +// SPDX-License-Identifier: GPL-3.0-or-later + +import type { GlobalSettings } from '../types'; + +import { DEFAULT_DARK_THEME_ID, DEFAULT_LIGHT_THEME_ID, DEFAULT_THEME_ID, getActiveThemeId } from '../themes'; + +/** Collects the General panel settings from the settings modal. */ +export function collectGeneralSettings( + root: HTMLElement, + base: GlobalSettings, + modeForTheme: (themeId: string) => 'light' | 'dark', +): GlobalSettings['general'] { + const get = (sel: string) => root.querySelector(sel); + const autoTheme = !!get('#set-theme-auto')?.checked; + const themePack = get('#set-theme')?.value || DEFAULT_LIGHT_THEME_ID; + const theme = autoTheme ? 'system' : modeForTheme(themePack); + + return { + ...base.general, + theme, + theme_pack: themePack || DEFAULT_LIGHT_THEME_ID, + language: get('#set-language')?.value, + default_backend: (get('#set-default-backend')?.value || 'git') as any, + update_channel: get('#set-update-channel')?.value || 'stable', + reopen_last_repos: !!get('#set-reopen-last')?.checked, + checks_on_launch: !!get('#set-checks-on-launch')?.checked, + crash_reports: !!get('#set-crash-reports')?.checked, + }; +} + +/** Loads the General panel settings into the settings modal form controls. */ +export async function loadGeneralSettingsIntoForm( + root: HTMLElement, + cfg: GlobalSettings, + toKebab: (value: unknown) => string, + refreshDefaultBackendOptions: (modal: HTMLElement, cfg: GlobalSettings) => Promise, + rebuildThemePackOptions: ( + themeSelect: HTMLSelectElement, + options: { desiredId: string; forceReload: boolean }, + ) => Promise, +): Promise { + const get = (sel: string) => root.querySelector(sel); + const themeSel = get('#set-theme'); + const elAuto = get('#set-theme-auto'); + const themePref = (cfg.general?.theme || 'system') as 'system' | 'light' | 'dark'; + if (elAuto) elAuto.checked = themePref === 'system'; + if (themeSel) { + let desiredId = String(cfg.general?.theme_pack || DEFAULT_LIGHT_THEME_ID); + if (desiredId.trim().toLowerCase() === DEFAULT_THEME_ID) { + desiredId = themePref === 'dark' ? DEFAULT_DARK_THEME_ID : DEFAULT_LIGHT_THEME_ID; + } + await rebuildThemePackOptions(themeSel, { + desiredId, + forceReload: true, + }); + themeSel.disabled = themePref === 'system'; + if (themePref === 'system') { + themeSel.value = getActiveThemeId() || themeSel.value; + } + } + + const elLang = get('#set-language'); + if (elLang) elLang.value = toKebab(cfg.general?.language); + await refreshDefaultBackendOptions(root, cfg); + const elChan = get('#set-update-channel'); + if (elChan) { + elChan.value = toKebab(cfg.general?.update_channel); + } + const elReo = get('#set-reopen-last'); + if (elReo) elReo.checked = !!cfg.general?.reopen_last_repos; + const elChk = get('#set-checks-on-launch'); + if (elChk) elChk.checked = !!cfg.general?.checks_on_launch; + const elCrash = get('#set-crash-reports'); + if (elCrash) elCrash.checked = !!cfg.general?.crash_reports; +} diff --git a/Frontend/src/scripts/lib/confirm.test.ts b/Frontend/src/scripts/lib/confirm.test.ts new file mode 100644 index 00000000..4de7b243 --- /dev/null +++ b/Frontend/src/scripts/lib/confirm.test.ts @@ -0,0 +1,80 @@ +// Copyright © 2025-2026 OpenVCS Contributors +// SPDX-License-Identifier: GPL-3.0-or-later + +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; + +import { confirmBool } from './confirm'; + +let originalConfirmDescriptor: PropertyDescriptor | undefined; + +/** Snapshots the current global confirm descriptor before each test. */ +beforeEach(() => { + originalConfirmDescriptor = Object.getOwnPropertyDescriptor(window, 'confirm'); +}); + +/** Restores the original global confirm implementation after each test. */ +afterEach(() => { + document.body.innerHTML = ''; + vi.restoreAllMocks(); + if (originalConfirmDescriptor) { + Object.defineProperty(window, 'confirm', originalConfirmDescriptor); + return; + } + Reflect.deleteProperty(window as unknown as Record, 'confirm'); +}); + +describe('confirmBool', () => { + it('uses the in-app modal when the modal root exists', async () => { + document.body.innerHTML = '
'; + + const pending = confirmBool('Discard changes?'); + const confirmBtn = document.getElementById('confirm-modal-confirm-btn') as HTMLButtonElement | null; + + expect(document.getElementById('confirm-modal')?.getAttribute('aria-hidden')).toBe('false'); + expect(confirmBtn?.textContent).toBe('Confirm'); + confirmBtn?.click(); + + await expect(pending).resolves.toBe(true); + }); + + it('invokes window.confirm with the window receiver', async () => { + document.body.innerHTML = ''; + const confirmSpy = vi.fn(function (this: Window, message: string) { + expect(this).toBe(window); + expect(message).toBe('Discard changes?'); + return true; + }); + + Object.defineProperty(window, 'confirm', { + configurable: true, + writable: true, + value: confirmSpy, + }); + + await expect(confirmBool('Discard changes?')).resolves.toBe(true); + }); + + it('supports promise-based confirm implementations', async () => { + document.body.innerHTML = ''; + Object.defineProperty(window, 'confirm', { + configurable: true, + writable: true, + value: vi.fn().mockResolvedValue({ confirmed: true }), + }); + + await expect(confirmBool('Discard changes?')).resolves.toBe(true); + }); + + it('returns false when confirm throws', async () => { + document.body.innerHTML = ''; + Object.defineProperty(window, 'confirm', { + configurable: true, + writable: true, + value: () => { + throw new TypeError('Illegal invocation'); + }, + }); + + await expect(confirmBool('Discard changes?')).resolves.toBe(false); + }); +}); diff --git a/Frontend/src/scripts/lib/confirm.ts b/Frontend/src/scripts/lib/confirm.ts index 9d7febed..06addc8e 100644 --- a/Frontend/src/scripts/lib/confirm.ts +++ b/Frontend/src/scripts/lib/confirm.ts @@ -1,6 +1,8 @@ // Copyright © 2025-2026 OpenVCS Contributors // SPDX-License-Identifier: GPL-3.0-or-later +import { confirmWithModal } from '../features/confirmModal'; + /** * Attempts to coerce arbitrary confirm-return payloads into a boolean. * @@ -29,18 +31,35 @@ function coerceConfirmResult(value: unknown): boolean { /** * Shows a confirmation prompt and returns a normalized boolean result. * - * Supports both synchronous browser `window.confirm` and Promise-returning - * confirm implementations exposed by embedded runtimes. + * Prefers the shared in-app confirmation modal when the modal host is mounted, + * then falls back to browser `window.confirm` for runtimes that do not render + * the main modal shell. * * @param message - Prompt text shown to the user. * @returns `true` when the user confirms; otherwise `false`. */ export async function confirmBool(message: string): Promise { + const modalRoot = document.getElementById('modals-root'); + if (modalRoot) { + try { + return await confirmWithModal({ + title: 'Confirm action', + message, + hint: 'This cannot be undone.', + confirmLabel: 'Confirm', + cancelLabel: 'Cancel', + danger: true, + }); + } catch { + // Fall through to browser confirm when modal rendering is unavailable. + } + } + const confirmFn = (window as any).confirm; if (typeof confirmFn !== 'function') return false; try { - const maybe = confirmFn(message) as unknown; + const maybe = Reflect.apply(confirmFn, window, [message]) as unknown; if (maybe && typeof (maybe as PromiseLike).then === 'function') { const resolved = await (maybe as PromiseLike); return coerceConfirmResult(resolved); diff --git a/Frontend/src/scripts/lib/logger.ts b/Frontend/src/scripts/lib/logger.ts index 36bac15e..f8a6d02b 100644 --- a/Frontend/src/scripts/lib/logger.ts +++ b/Frontend/src/scripts/lib/logger.ts @@ -2,8 +2,10 @@ // SPDX-License-Identifier: GPL-3.0-or-later import { TAURI } from "./tauri"; +import { addFrontendLogBreadcrumb } from "./monitoring"; type LogLevel = "trace" | "debug" | "info" | "warn" | "error"; +const LOGGER_PATCH_FLAG = "__OPENVCS_FRONTEND_LOGGER_INSTALLED__"; interface Logger { trace: (...args: unknown[]) => void; @@ -31,12 +33,34 @@ function formatMessage(...args: unknown[]): string { .join(" "); } -function sendToBackend(level: LogLevel, message: string): void { +function sendToBackend( + level: LogLevel, + message: string, + options: { breadcrumb?: boolean } = {}, +): void { + if (options.breadcrumb !== false) { + addFrontendLogBreadcrumb(toMonitoringBreadcrumbLevel(level), message); + } if (TAURI.has) { TAURI.invoke("log_frontend_message", { level, message }).catch(() => {}); } } +/** Maps logger levels to the breadcrumb levels sent through monitoring relay payloads. */ +function toMonitoringBreadcrumbLevel(level: LogLevel): "debug" | "info" | "warning" | "error" { + switch (level) { + case "trace": + case "debug": + return "debug"; + case "info": + return "info"; + case "warn": + return "warning"; + case "error": + return "error"; + } +} + function createLogger(module: string): Logger { const prefix = `[${module}]`; @@ -65,37 +89,56 @@ function createLogger(module: string): Logger { } function installFrontendLogger(): void { + if ((globalThis as Record)[LOGGER_PATCH_FLAG]) { + return; + } + const originalConsole = { - debug: console.debug, - log: console.log, - warn: console.warn, - error: console.error, + debug: console.debug.bind(console), + info: console.info.bind(console), + log: console.log.bind(console), + warn: console.warn.bind(console), + error: console.error.bind(console), + trace: console.trace.bind(console), }; console.debug = (...args: unknown[]) => { const msg = formatMessage(...args); sendToBackend("debug", msg); + originalConsole.debug(...args); + }; + + console.info = (...args: unknown[]) => { + const msg = formatMessage(...args); + sendToBackend("info", msg); + originalConsole.info(...args); }; console.log = (...args: unknown[]) => { const msg = formatMessage(...args); sendToBackend("info", msg); + originalConsole.log(...args); }; console.warn = (...args: unknown[]) => { const msg = formatMessage(...args); sendToBackend("warn", msg); + originalConsole.warn(...args); }; console.error = (...args: unknown[]) => { const msg = formatMessage(...args); sendToBackend("error", msg); + originalConsole.error(...args); }; console.trace = (...args: unknown[]) => { const msg = formatMessage(...args); sendToBackend("trace", msg); + originalConsole.trace(...args); }; + + (globalThis as Record)[LOGGER_PATCH_FLAG] = true; } installFrontendLogger(); diff --git a/Frontend/src/scripts/lib/monitoring.test.ts b/Frontend/src/scripts/lib/monitoring.test.ts new file mode 100644 index 00000000..daa1a033 --- /dev/null +++ b/Frontend/src/scripts/lib/monitoring.test.ts @@ -0,0 +1,152 @@ +// Copyright © 2025-2026 OpenVCS Contributors +// SPDX-License-Identifier: GPL-3.0-or-later + +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; + +interface MonitoringModule { + normalizeOptionalString: (value: string | null | undefined) => string | null; + isFrontendMonitoringAllowed: (settings: unknown) => boolean; + shouldEnableFrontendMonitoring: (settings: unknown) => boolean; + syncFrontendMonitoring: (settings: unknown) => Promise; + addFrontendLogBreadcrumb: (level: 'debug' | 'info' | 'warning' | 'error', message: string) => void; +} + +beforeEach(() => { + vi.resetModules(); + vi.restoreAllMocks(); + delete (window as Window & { __TAURI__?: unknown }).__TAURI__; +}); + +afterEach(() => { + vi.unstubAllEnvs(); +}); + +describe('normalizeOptionalString', () => { + it('returns null for blank strings', async () => { + const monitoring = await import('./monitoring'); + expect(monitoring.normalizeOptionalString(' ')).toBeNull(); + }); + + it('trims populated strings', async () => { + const monitoring = await import('./monitoring'); + expect(monitoring.normalizeOptionalString(' value ')).toBe('value'); + }); +}); + +describe('isFrontendMonitoringAllowed', () => { + it('requires crash report consent', async () => { + const monitoring = await import('./monitoring'); + expect(monitoring.isFrontendMonitoringAllowed({ general: { crash_reports: true } })).toBe(true); + expect(monitoring.isFrontendMonitoringAllowed({ general: { crash_reports: false } })).toBe(false); + expect(monitoring.isFrontendMonitoringAllowed(null)).toBe(false); + }); +}); + +describe('shouldEnableFrontendMonitoring', () => { + it('requires tauri and crash report consent', async () => { + const monitoring = await import('./monitoring'); + expect(monitoring.shouldEnableFrontendMonitoring({ general: { crash_reports: true } })).toBe(false); + expect(monitoring.shouldEnableFrontendMonitoring({ general: { crash_reports: false } })).toBe(false); + }); +}); + +describe('syncFrontendMonitoring', () => { + it('relays frontend errors with capped breadcrumbs', async () => { + const invoke = vi.fn().mockResolvedValue(undefined); + (window as Window & { __TAURI__?: unknown }).__TAURI__ = { + core: { invoke }, + event: { listen: vi.fn() }, + }; + + const monitoring = (await import('./monitoring')) as MonitoringModule; + await monitoring.syncFrontendMonitoring({ general: { crash_reports: true } }); + + for (let index = 0; index < 45; index += 1) { + monitoring.addFrontendLogBreadcrumb('info', `crumb-${index}`); + } + + const error = new Error('boom'); + window.dispatchEvent(new ErrorEvent('error', { error, message: error.message, filename: 'app://index.js', lineno: 7, colno: 11 })); + + expect(invoke).toHaveBeenCalledTimes(1); + const [, payload] = invoke.mock.calls[0] ?? []; + expect(payload?.payload?.message).toBe('boom'); + expect(payload?.payload?.kind).toBe('error'); + expect(payload?.payload?.breadcrumbs).toHaveLength(40); + expect(payload?.payload?.breadcrumbs[0]?.message).toBe('crumb-5'); + expect(payload?.payload?.breadcrumbs[39]?.message).toBe('crumb-44'); + }); + + it('uninstalls listeners when crash reports are disabled', async () => { + const invoke = vi.fn().mockResolvedValue(undefined); + (window as Window & { __TAURI__?: unknown }).__TAURI__ = { + core: { invoke }, + event: { listen: vi.fn() }, + }; + + const monitoring = (await import('./monitoring')) as MonitoringModule; + await monitoring.syncFrontendMonitoring({ general: { crash_reports: true } }); + await monitoring.syncFrontendMonitoring({ general: { crash_reports: false } }); + + window.dispatchEvent(new ErrorEvent('error', { error: new Error('after-disable'), message: 'after-disable' })); + expect(invoke).not.toHaveBeenCalled(); + }); + + it('reports a fallback message for undefined rejection reasons', async () => { + const invoke = vi.fn().mockResolvedValue(undefined); + (window as Window & { __TAURI__?: unknown }).__TAURI__ = { + core: { invoke }, + event: { listen: vi.fn() }, + }; + + const monitoring = (await import('./monitoring')) as MonitoringModule; + await monitoring.syncFrontendMonitoring({ general: { crash_reports: true } }); + + const event = new Event('unhandledrejection') as PromiseRejectionEvent & { reason: unknown }; + Object.defineProperty(event, 'reason', { + value: undefined, + configurable: true, + }); + window.dispatchEvent(event); + + expect(invoke).toHaveBeenCalledTimes(1); + const [, payload] = invoke.mock.calls[0] ?? []; + expect(payload?.payload?.kind).toBe('unhandledrejection'); + expect(payload?.payload?.message).toBe('Unhandled promise rejection (no reason provided)'); + }); + + it('forwards build metadata with frontend reports', async () => { + vi.stubEnv('VITE_SENTRY_RELEASE', 'release-123'); + vi.stubEnv('VITE_SENTRY_ENVIRONMENT', 'qa'); + + const invoke = vi.fn().mockResolvedValue(undefined); + (window as Window & { __TAURI__?: unknown }).__TAURI__ = { + core: { invoke }, + event: { listen: vi.fn() }, + }; + + const monitoring = (await import('./monitoring')) as MonitoringModule; + await monitoring.syncFrontendMonitoring({ general: { crash_reports: true } }); + + window.dispatchEvent(new ErrorEvent('error', { error: new Error('meta'), message: 'meta' })); + + const [, payload] = invoke.mock.calls[0] ?? []; + expect(payload?.payload?.release).toBe('release-123'); + expect(payload?.payload?.environment).toBe('qa'); + }); + + it('is idempotent when enabling monitoring repeatedly', async () => { + const invoke = vi.fn().mockResolvedValue(undefined); + (window as Window & { __TAURI__?: unknown }).__TAURI__ = { + core: { invoke }, + event: { listen: vi.fn() }, + }; + + const monitoring = (await import('./monitoring')) as MonitoringModule; + await monitoring.syncFrontendMonitoring({ general: { crash_reports: true } }); + await monitoring.syncFrontendMonitoring({ general: { crash_reports: true } }); + + window.dispatchEvent(new ErrorEvent('error', { error: new Error('once'), message: 'once' })); + expect(invoke).toHaveBeenCalledTimes(1); + }); +}); diff --git a/Frontend/src/scripts/lib/monitoring.ts b/Frontend/src/scripts/lib/monitoring.ts new file mode 100644 index 00000000..4cab0768 --- /dev/null +++ b/Frontend/src/scripts/lib/monitoring.ts @@ -0,0 +1,195 @@ +// Copyright © 2025-2026 OpenVCS Contributors +// SPDX-License-Identifier: GPL-3.0-or-later + +import type { GlobalSettings } from '../types'; + +import { TAURI } from './tauri'; + +/** Represents the console breadcrumb level forwarded with frontend error reports. */ +type MonitoringBreadcrumbLevel = 'debug' | 'info' | 'warning' | 'error'; + +/** Maximum number of frontend breadcrumbs kept with an error report. */ +const MAX_FRONTEND_BREADCRUMBS = 40; +/** Sentry release metadata passed through the backend relay. */ +const FRONTEND_RELEASE = normalizeOptionalString(import.meta.env.VITE_SENTRY_RELEASE); +/** Sentry environment metadata passed through the backend relay. */ +const FRONTEND_ENVIRONMENT = + normalizeOptionalString(import.meta.env.VITE_SENTRY_ENVIRONMENT) ?? 'desktop'; + +/** Represents a single frontend breadcrumb included with a reported error. */ +interface FrontendBreadcrumb { + timestampMs: number; + level: MonitoringBreadcrumbLevel; + message: string; +} + +/** Represents a normalized frontend error payload sent to the backend. */ +interface FrontendErrorReport { + kind: 'error' | 'unhandledrejection'; + message: string; + stack?: string | null; + source?: string | null; + line?: number | null; + column?: number | null; + url?: string | null; + userAgent?: string | null; + release?: string | null; + environment?: string | null; + breadcrumbs: FrontendBreadcrumb[]; +} + +/** Tracks whether frontend monitoring listeners are currently installed. */ +let monitoringEnabled = false; +/** Stores recent console breadcrumbs for the next relayed frontend error. */ +const frontendBreadcrumbs: FrontendBreadcrumb[] = []; + +/** Handles global frontend `error` events. */ +let errorListener: ((event: ErrorEvent) => void) | null = null; +/** Handles global frontend `unhandledrejection` events. */ +let rejectionListener: ((event: PromiseRejectionEvent) => void) | null = null; + +/** Trims a string-like value and normalizes empty strings to `null`. */ +export function normalizeOptionalString(value: string | null | undefined): string | null { + const normalized = value?.trim(); + return normalized ? normalized : null; +} + +/** Returns whether current settings allow frontend crash reporting. */ +export function isFrontendMonitoringAllowed(settings: GlobalSettings | null | undefined): boolean { + return settings?.general?.crash_reports === true; +} + +/** Returns whether the frontend has everything needed to relay monitoring events. */ +export function shouldEnableFrontendMonitoring( + settings: GlobalSettings | null | undefined, +): boolean { + return Boolean(TAURI.has && isFrontendMonitoringAllowed(settings)); +} + +/** Synchronizes frontend error relay hooks with the latest settings. */ +export async function syncFrontendMonitoring( + settings: GlobalSettings | null | undefined, +): Promise { + const shouldEnable = shouldEnableFrontendMonitoring(settings); + + if (!shouldEnable) { + uninstallFrontendMonitoring(); + return; + } + + if (monitoringEnabled) { + return; + } + + installFrontendMonitoring(); +} + +/** Records a console breadcrumb when frontend monitoring is active. */ +export function addFrontendLogBreadcrumb( + level: MonitoringBreadcrumbLevel, + message: string, +): void { + if (!monitoringEnabled) { + return; + } + + frontendBreadcrumbs.push({ + timestampMs: Date.now(), + level, + message, + }); + if (frontendBreadcrumbs.length > MAX_FRONTEND_BREADCRUMBS) { + frontendBreadcrumbs.splice(0, frontendBreadcrumbs.length - MAX_FRONTEND_BREADCRUMBS); + } +} + +/** Installs global frontend error listeners that relay events to the backend. */ +function installFrontendMonitoring(): void { + errorListener = (event: ErrorEvent) => { + void reportFrontendError({ + kind: 'error', + message: event.error instanceof Error ? event.error.message : String(event.message || 'Unknown error'), + stack: event.error instanceof Error ? event.error.stack ?? null : null, + source: normalizeOptionalString(event.filename), + line: typeof event.lineno === 'number' ? event.lineno : null, + column: typeof event.colno === 'number' ? event.colno : null, + }); + }; + + rejectionListener = (event: PromiseRejectionEvent) => { + const rejection = event.reason; + void reportFrontendError({ + kind: 'unhandledrejection', + message: getUnhandledRejectionMessage(rejection), + stack: rejection instanceof Error ? rejection.stack ?? null : null, + source: null, + line: null, + column: null, + }); + }; + + window.addEventListener('error', errorListener); + window.addEventListener('unhandledrejection', rejectionListener); + monitoringEnabled = true; +} + +/** Removes global frontend monitoring listeners and clears queued breadcrumbs. */ +function uninstallFrontendMonitoring(): void { + if (errorListener) { + window.removeEventListener('error', errorListener); + errorListener = null; + } + if (rejectionListener) { + window.removeEventListener('unhandledrejection', rejectionListener); + rejectionListener = null; + } + frontendBreadcrumbs.length = 0; + monitoringEnabled = false; +} + +/** Reports a normalized frontend error payload to the backend monitoring command. */ +async function reportFrontendError( + report: Omit, +): Promise { + if (!monitoringEnabled || !TAURI.has) { + return; + } + + const payload: FrontendErrorReport = { + ...report, + url: normalizeOptionalString(window.location.href), + userAgent: normalizeOptionalString(window.navigator.userAgent), + release: FRONTEND_RELEASE, + environment: FRONTEND_ENVIRONMENT, + breadcrumbs: frontendBreadcrumbs.slice(), + }; + + await TAURI.invoke('report_frontend_error', { payload }).catch(() => {}); +} + +/** Derives a human-readable message for an unhandled promise rejection reason. */ +function getUnhandledRejectionMessage(reason: unknown): string { + if (reason instanceof Error) { + return reason.message; + } + if (typeof reason === 'string') { + const normalized = normalizeOptionalString(reason); + if (normalized) { + return normalized; + } + } + try { + const serialized = JSON.stringify(reason); + if (typeof serialized === 'string' && serialized.trim().length > 0) { + return serialized; + } + } catch { + // Fall through to the generic formatter below. + } + + if (typeof reason === 'undefined') { + return 'Unhandled promise rejection (no reason provided)'; + } + + return String(reason); +} diff --git a/Frontend/src/scripts/main.ts b/Frontend/src/scripts/main.ts index 7f231a83..bdeb796f 100644 --- a/Frontend/src/scripts/main.ts +++ b/Frontend/src/scripts/main.ts @@ -1,7 +1,9 @@ // Copyright © 2025-2026 OpenVCS Contributors // SPDX-License-Identifier: GPL-3.0-or-later import './lib/logger'; +import { syncFrontendMonitoring } from './lib/monitoring'; import { TAURI } from './lib/tauri'; +import type { GlobalSettings } from './types'; import { qs } from './lib/dom'; import { notify } from './lib/notify'; import { setStatus } from './lib/status'; @@ -14,7 +16,7 @@ import { import { clearPluginMenubarMenus, initMenubar, refreshPluginMenubarMenus } from './ui/menubar'; import { closeAllModals } from './ui/modals'; import { bindCommandSheet, openSheet, closeSheet } from './features/commandSheet'; -import { bindRepoHotkeys, bindFilter, renderList, wireRenderListCallbacks, hydrateBranches, hydrateStatus, hydrateCommits, hydrateStash } from './features/repo'; +import { bindRepoHotkeys, bindFilter, renderList, wireRenderListCallbacks, hydrateBranches, hydrateStatus, hydrateCommits, hydrateStash, yieldToPaint } from './features/repo'; import { bindBranchUI } from './features/branches'; import { bindCommit } from './features/diff'; import { openAbout } from './features/about'; @@ -42,9 +44,12 @@ const commitBtn = qs('#commit-btn'); const undoLeftBtn = qs('#undo-left-btn'); let fetchCloseTimer: number | null = null; let pluginMenuRefreshTimer: number | null = null; + /** Matches the fetch popover close animation so the element hides after the transition finishes. */ const FETCH_CLOSE_MS = 130; + /** Gives repo-open plugin state time to settle before rebuilding contributed menubar items. */ + const PLUGIN_MENU_REFRESH_SETTLE_MS = 400; - /** Schedules a delayed plugin menubar refresh to avoid repo-open races. */ + /** Schedules a delayed plugin menubar refresh to avoid repo-open races while plugin state settles. */ function schedulePluginMenuRefresh(delayMs = 250) { if (pluginMenuRefreshTimer !== null) { window.clearTimeout(pluginMenuRefreshTimer); @@ -94,6 +99,9 @@ function forceCloseTransientUi() { /** Boots the frontend shell, wires handlers, and hydrates initial state. */ async function boot() { + const cfg = await loadInitialGlobalSettings(); + await syncFrontendMonitoring(cfg); + // If launched as the Output Log window, render that view and skip the main app UI. if (await initOutputLogViewIfRequested()) return; initOverlayScrollbarsFor(document); @@ -101,10 +109,9 @@ async function boot() { await initPlugins(); // theme & basic layout // Prefer native settings for theme; fall back to current in-memory default - if (TAURI.has) { + if (cfg) { (async () => { try { - const cfg = await TAURI.invoke('get_global_settings'); const themeMode = cfg?.general?.theme as ('dark'|'light'|'system'|undefined); const modeForPack = themeMode ?? 'system'; const themePack = String(cfg?.general?.theme_pack || DEFAULT_LIGHT_THEME_ID); @@ -184,7 +191,8 @@ async function boot() { await TAURI.invoke('git_fetch', {}); notify('Fetched'); if (hydrate) { - await Promise.allSettled([hydrateStatus(), hydrateCommits()]); + await yieldToPaint(); + void Promise.allSettled([hydrateStatus(), hydrateCommits()]); } success = true; } catch { @@ -207,7 +215,8 @@ async function boot() { await TAURI.invoke('git_fetch_all', {}); notify('Fetched all remotes'); if (hydrate) { - await Promise.allSettled([hydrateStatus(), hydrateCommits()]); + await yieldToPaint(); + void Promise.allSettled([hydrateStatus(), hydrateCommits()]); } success = true; } catch { @@ -381,14 +390,22 @@ async function boot() { break; case '__plugin_menu_action__': { if (!TAURI.has) { notify('Plugin actions are available in the desktop app'); break; } - const pluginId = String(payload?.pluginId || '').trim(); - const actionId = String(payload?.actionId || '').trim(); - if (!pluginId || !actionId) break; + const pluginId = typeof payload?.pluginId === 'string' ? payload.pluginId.trim() : ''; + const actionId = typeof payload?.actionId === 'string' ? payload.actionId.trim() : ''; + if (!pluginId || !actionId) { + console.warn(`Plugin menu action skipped: missing pluginId (${!!pluginId}) or actionId (${!!actionId})`); + notify(!pluginId && !actionId + ? 'Plugin action is missing plugin and action IDs' + : !pluginId + ? 'Plugin action missing plugin ID' + : `Plugin action missing action for "${pluginId}"`); + break; + } try { await invokePluginAction(pluginId, actionId); } catch (e) { console.error(`Plugin menu action failed: ${pluginId}/${actionId}`, e); - notify('Plugin action failed'); + notify(`Plugin action for "${pluginId}" failed`); } break; } @@ -450,7 +467,7 @@ async function boot() { initMenubar(runMenuAction); refreshPluginMenubarMenus().catch(() => {}); - schedulePluginMenuRefresh(400); + schedulePluginMenuRefresh(PLUGIN_MENU_REFRESH_SETTLE_MS); TAURI.listen?.('menu', async ({ payload: id }) => { const resolved = typeof id === 'string' ? id : String(id ?? ''); @@ -461,6 +478,7 @@ async function boot() { // Global busy indicator for any Git activity (function(){ let busyTimer: any = null; + let busyFrame: number | null = null; const setBusy = (msg: string, showSpinner = true) => { const s = document.getElementById('status'); if (!s) return; @@ -474,13 +492,20 @@ async function boot() { s.textContent = 'Ready'; }, 1500); }; + const queueBusyUpdate = () => { + if (busyFrame !== null) return; + busyFrame = window.requestAnimationFrame(() => { + busyFrame = null; + const focused = document.visibilityState === 'visible' && document.hasFocus(); + setBusy('Working…', focused); + }); + }; TAURI.listen?.('git-progress', ({ payload }) => { // Don't spam the footer with raw git output; keep it generic. void payload; // Avoid spinner-driven repaint churn for passive/background progress. // Explicit user actions already set busy state via their own controllers. - const focused = document.visibilityState === 'visible' && document.hasFocus(); - setBusy('Working…', focused); + queueBusyUpdate(); }); })(); @@ -650,4 +675,17 @@ async function boot() { }); } +/** Loads persisted global settings for bootstrap-time features such as theming and monitoring. */ +async function loadInitialGlobalSettings(): Promise { + if (!TAURI.has) { + return null; + } + + try { + return await TAURI.invoke('get_global_settings'); + } catch { + return null; + } +} + boot(); diff --git a/Frontend/src/scripts/ui/menubar.ts b/Frontend/src/scripts/ui/menubar.ts index 0bf34ddc..d748972f 100644 --- a/Frontend/src/scripts/ui/menubar.ts +++ b/Frontend/src/scripts/ui/menubar.ts @@ -11,6 +11,7 @@ interface PluginMenuPayload { plugin_id: string; id: string; label: string; + surface: 'menubar' | 'settings'; elements: Array<{ type: 'text' | 'button' | string; id?: string; @@ -57,6 +58,10 @@ export async function refreshPluginMenubarMenus(): Promise { } for (const menu of Array.isArray(menus) ? menus : []) { + // Only render menubar-surface menus in the menubar. + const surface = String(menu.surface || 'menubar').toLowerCase(); + if (surface !== 'menubar') continue; + const menuId = String(menu?.id || '').trim(); const list = getMenuList(menuId); if (!menuId || !list) continue; diff --git a/Frontend/src/scripts/ui/modals.ts b/Frontend/src/scripts/ui/modals.ts index 3f8a9a60..51ebed53 100644 --- a/Frontend/src/scripts/ui/modals.ts +++ b/Frontend/src/scripts/ui/modals.ts @@ -19,6 +19,8 @@ import { wireRenameBranch } from "../features/renameBranch"; import cherryPickHtml from "@modals/cherry-pick.html?raw"; import { wireCherryPick } from "../features/cherryPick"; import deleteBranchHtml from "@modals/delete-branch.html?raw"; +import confirmHtml from "@modals/confirm.html?raw"; +import { wireConfirmModal } from "../features/confirmModal"; import { wireDeleteBranchConfirm } from "../features/deleteBranchConfirm"; import setUpstreamHtml from "@modals/set-upstream.html?raw"; import { wireSetUpstream } from "../features/setUpstream"; @@ -44,6 +46,7 @@ const FRAGMENTS: Record = { "new-branch-modal": newBranchHtml, "rename-branch-modal": renameBranchHtml, "cherry-pick-modal": cherryPickHtml, + "confirm-modal": confirmHtml, "delete-branch-modal": deleteBranchHtml, "set-upstream-modal": setUpstreamHtml, "update-modal": updateHtml, @@ -53,7 +56,10 @@ const FRAGMENTS: Record = { }; const loaded = new Set(); -const root = qs("#modals-root"); +/** Returns the modal root where lazily-hydrated fragments are injected. */ +function getRoot(): HTMLElement | null { + return qs('#modals-root'); +} // scroll lock counter (supports multiple modals) let openCount = 0; @@ -66,6 +72,22 @@ function unlockScroll() { if (openCount === 0) document.body.style.overflow = ""; } +/** Updates a modal's hidden state and emits lifecycle events when visibility changes. */ +function setModalHidden(el: HTMLElement, hidden: boolean) { + const wasHidden = el.getAttribute("aria-hidden") !== "false"; + const nextHidden = hidden ? "true" : "false"; + if (el.getAttribute("aria-hidden") !== nextHidden) { + el.setAttribute("aria-hidden", nextHidden); + } + if (!hidden && wasHidden) { + el.dispatchEvent(new CustomEvent("modal:opened")); + } + if (hidden && !wasHidden) { + el.dispatchEvent(new CustomEvent("modal:closed")); + } + return wasHidden; +} + function closeWithAnimation(id: string, el: HTMLElement) { const reduceMotion = window.matchMedia("(prefers-reduced-motion: reduce)").matches; if (reduceMotion) { @@ -90,6 +112,7 @@ export function hydrate(id: string): void { loaded.add(id); return; } + const root = getRoot(); if (!root || loaded.has(id)) return; const html = FRAGMENTS[id]; @@ -110,6 +133,7 @@ export function hydrate(id: string): void { if (id === "new-branch-modal") wireNewBranch(); if (id === "rename-branch-modal") wireRenameBranch(); if (id === "cherry-pick-modal") wireCherryPick(); + if (id === "confirm-modal") wireConfirmModal(); if (id === "delete-branch-modal") wireDeleteBranchConfirm(); if (id === "set-upstream-modal") wireSetUpstream(); if (id === "update-modal") wireUpdate(); @@ -133,8 +157,8 @@ export function openModal(id: string): void { window.clearTimeout(existing); (el as any).__animatedCloseTimer = undefined; } - el.setAttribute("aria-hidden", "false"); - lockScroll(); + const wasHidden = setModalHidden(el, false); + if (wasHidden) lockScroll(); refreshOverlayScrollbarsFor(el); // Click-to-close once @@ -153,7 +177,7 @@ export function closeModal(id: string): void { const el = document.getElementById(id); if (!el) return; if (el.getAttribute("aria-hidden") !== "true") { - el.setAttribute("aria-hidden", "true"); + setModalHidden(el, true); unlockScroll(); } } @@ -169,7 +193,7 @@ export function closeAllModals(): void { (el as any).__animatedCloseTimer = undefined; } el.classList.remove("is-closing"); - el.setAttribute("aria-hidden", "true"); + setModalHidden(el, true); } openCount = 0; document.body.style.overflow = ""; diff --git a/Frontend/src/styles/components.css b/Frontend/src/styles/components.css index a74eee4a..a1d48718 100644 --- a/Frontend/src/styles/components.css +++ b/Frontend/src/styles/components.css @@ -350,7 +350,11 @@ button.saved-state,.btn.saved-state,.tbtn.saved-state{ #status.busy::after{ content:""; display:inline-block; width:12px; height:12px; margin-left:.5rem; border:2px solid var(--muted); border-top-color:var(--accent); - border-radius:50%; vertical-align:-2px; + border-radius:50%; vertical-align:-2px; animation:status-spin .8s linear infinite; +} + +@keyframes status-spin{ + to{ transform:rotate(360deg); } } /* ========== Branch switcher (button only) ========== */ diff --git a/Frontend/src/styles/index.css b/Frontend/src/styles/index.css index 0790b19e..6acd72d3 100644 --- a/Frontend/src/styles/index.css +++ b/Frontend/src/styles/index.css @@ -14,6 +14,7 @@ @import "./modal/repo-settings.css"; @import "./output-log.css"; @import "./modal/new-branch.css"; +@import "./modal/confirm.css"; @import "./modal/stash-confirm.css"; @import "./modal/merge.css"; @import "./modal/ssh-keys.css"; diff --git a/Frontend/src/styles/modal/confirm.css b/Frontend/src/styles/modal/confirm.css new file mode 100644 index 00000000..f135074d --- /dev/null +++ b/Frontend/src/styles/modal/confirm.css @@ -0,0 +1,10 @@ +/* src/styles/modal/confirm.css */ + +#confirm-modal .dialog.sheet { + width: clamp(420px, 92vw, 520px); + max-height: min(70vh, 360px); +} + +#confirm-modal #confirm-modal-message { + white-space: pre-wrap; +} diff --git a/Frontend/src/vite-env.d.ts b/Frontend/src/vite-env.d.ts new file mode 100644 index 00000000..774aa03a --- /dev/null +++ b/Frontend/src/vite-env.d.ts @@ -0,0 +1,14 @@ +// Copyright © 2025-2026 OpenVCS Contributors +// SPDX-License-Identifier: GPL-3.0-or-later +/// + +interface ImportMetaEnv { + readonly VITE_SENTRY_ENVIRONMENT?: string; + readonly VITE_SENTRY_RELEASE?: string; +} + +interface ImportMeta { + readonly env: ImportMetaEnv; +} + +export {}; diff --git a/Frontend/vite.config.mts b/Frontend/vite.config.mts index 720eb217..e6d8c16b 100644 --- a/Frontend/vite.config.mts +++ b/Frontend/vite.config.mts @@ -1,10 +1,17 @@ +// Copyright © 2025-2026 OpenVCS Contributors +// SPDX-License-Identifier: GPL-3.0-or-later /// -// vite.config.ts +import { sentryVitePlugin } from "@sentry/vite-plugin"; import { defineConfig } from "vite"; import { fileURLToPath, URL } from "node:url"; const host = process.env.TAURI_DEV_HOST; +const sentryAuthToken = process.env.SENTRY_AUTH_TOKEN?.trim(); +const sentryOrg = process.env.SENTRY_ORG?.trim(); +const sentryProject = process.env.SENTRY_PROJECT?.trim(); +const sentryRelease = process.env.VITE_SENTRY_RELEASE?.trim(); +const shouldUploadSourceMaps = Boolean(sentryAuthToken && sentryOrg && sentryProject && sentryRelease); export default defineConfig({ base: "./", // critical for packaged Tauri paths @@ -36,8 +43,27 @@ export default defineConfig({ target: "es2022", outDir: "dist", emptyOutDir: true, + sourcemap: shouldUploadSourceMaps ? "hidden" : false, }, optimizeDeps: { include: [], }, + plugins: shouldUploadSourceMaps + ? [ + sentryVitePlugin({ + authToken: sentryAuthToken, + org: sentryOrg, + project: sentryProject, + release: { + name: sentryRelease, + create: true, + finalize: true, + }, + sourcemaps: { + assets: "./dist/**", + }, + telemetry: false, + }), + ] + : [], }); diff --git a/README.md b/README.md index ff1e881c..510340ec 100644 --- a/README.md +++ b/README.md @@ -174,9 +174,10 @@ use the same channel-aware Tauri build flow. The Tauri precommands in `Backend/tauri.conf.json` intentionally set an explicit hook `cwd` to `Backend/` so Tauri does not resolve them from nested -plugin directories, and built-in plugins are materialized from -`openvcs.plugins.json` into `target/openvcs/built-in-plugins/` before the app is -built. +plugin directories, and built-in plugins are materialized from the +channel-aware `openvcs.plugins.json` into `target/openvcs/built-in-plugins/` +before the app is built. Local development can optionally override the active +channel list with `openvcs.plugins.local.json`. ### Optional: Rust‑only build diff --git a/docs/plugin architecture.md b/docs/plugin architecture.md index 030266a4..1585b608 100644 --- a/docs/plugin architecture.md +++ b/docs/plugin architecture.md @@ -48,6 +48,13 @@ match built-in top-level menus such as `repository` are projected into the main menubar. For VCS backend plugins, these items therefore appear only after the repository-scoped runtime has started. +Menu surfaces can be explicitly targeted using the `surface` option: + +- `getOrCreateMenu('repository', 'Repository', { surface: 'menubar' })` - renders in the top menubar +- `getOrCreateMenu('my-settings', 'My Settings', { surface: 'settings' })` - renders in the Settings modal + +The `surface` option is required. Plugin authors must explicitly specify where their menus should appear. + Core plugin-to-host notifications: - `host.log` @@ -56,6 +63,10 @@ Core plugin-to-host notifications: - `host.event_emit` - `vcs.event` +Selected-file commit flows stage repository-relative paths into the index with +`vcs.stage_paths` before issuing `vcs.commit`. Plugins implementing selected-path +commits should therefore support both RPCs consistently. + Plugin runtime requires the app-bundled Node binary; there is no fallback to a system `node` executable. @@ -64,9 +75,31 @@ system `node` executable. Instead, OpenVCS resolves config-declared plugin sources into the writable local plugin store before discovery runs: -- Built-in source list: `Client/openvcs.plugins.json` +- Built-in source list: `Client/openvcs.plugins.json` (channel-first) - User source list: top-level `plugin = [...]` in `openvcs.conf` +The built-in config uses a channel-first schema: + +```json +{ + "stable": ["@openvcs/git-plugin@latest", "@openvcs/official-themes@latest"], + "beta": ["@openvcs/git-plugin@beta", "@openvcs/official-themes@beta"], + "dev": ["@openvcs/git-plugin@edge", "@openvcs/official-themes@nightly"] +} +``` + +The active channel is determined by `OPENVCS_UPDATE_CHANNEL`: +- `stable` - production releases +- `beta` - beta builds +- `dev` - development builds +- `nightly` - alias for `dev` +- unset/unknown values default to `stable` + +For local development, create `openvcs.plugins.local.json` in the Client directory +to override the channel list (gitignored). If the file defines the active +channel key, that list fully replaces the committed channel list, including an +empty array. + The resolver accepts: - npm package specifiers such as `@scope/name` or `name@version` @@ -77,7 +110,7 @@ contents and then installs runtime dependencies into the local plugin root when needed. Local path plugins therefore behave like npm packages and should define their published files and `prepack` behavior accordingly. -## Installed Plugin Layout +## Installed Layout After sync, the backend operates only on local installed plugin directories: diff --git a/docs/plugins.md b/docs/plugins.md index c64361b6..faf65c54 100644 --- a/docs/plugins.md +++ b/docs/plugins.md @@ -8,11 +8,46 @@ directory under the app config directory. ## Source Of Truth -OpenVCS reads two plugin source lists with the same entry shape: +OpenVCS reads two plugin source lists: -- Built-in plugins: `Client/openvcs.plugins.json` +- Built-in plugins: `Client/openvcs.plugins.json` (channel-first) - User plugins: the top-level `plugin = [...]` array in `openvcs.conf` +## Channel-Based Built-in Config + +Built-in plugins use a channel-first schema that maps release channels to plugin +specifier arrays: + +```json +{ + "stable": ["@openvcs/git-plugin@latest", "@openvcs/official-themes@latest"], + "beta": ["@openvcs/git-plugin@beta", "@openvcs/official-themes@beta"], + "dev": ["@openvcs/git-plugin@edge", "@openvcs/official-themes@nightly"] +} +``` + +The active channel is determined by the `OPENVCS_UPDATE_CHANNEL` environment +variable: + +- `stable` - production releases +- `beta` - beta builds +- `dev` - development builds +- `nightly` - alias for `dev` +- unset/unknown values default to `stable` + +### Local Override + +For local development, create `openvcs.plugins.local.json` in the Client directory +with the same shape to override the channel-specific plugin list. This is useful +for testing different plugin versions without modifying the main config. + +If the local file defines the active channel key, that channel list fully +replaces the committed list, including an empty array. + +The local override file is ignored by git (see `.gitignore`). + +## User Plugin Config + Example user config: ```toml diff --git a/openvcs.plugins.json b/openvcs.plugins.json index 4dccff0e..0408ba46 100644 --- a/openvcs.plugins.json +++ b/openvcs.plugins.json @@ -1,6 +1,14 @@ { - "plugin": [ - "@openvcs/git-plugin@nightly", + "stable": [ + "@openvcs/git-plugin@latest", + "@openvcs/official-themes@latest" + ], + "beta": [ + "@openvcs/git-plugin@beta", + "@openvcs/official-themes@beta" + ], + "dev": [ + "@openvcs/git-plugin@edge", "@openvcs/official-themes@nightly" ] }