diff --git a/.env.example b/.env.example index 6b432ccdb..6caa3c7b4 100644 --- a/.env.example +++ b/.env.example @@ -163,6 +163,11 @@ OPENHUMAN_SKILLS_WORKING_MEMORY_ENABLED=true # --------------------------------------------------------------------------- # [optional] Sentry DSN for Rust core error reporting (no PII is sent) OPENHUMAN_SENTRY_DSN= +# [optional] Short git SHA baked into the Sentry release tag +# (`openhuman@+`) via `option_env!("OPENHUMAN_BUILD_SHA")`. +# CI sets this automatically; leave blank locally (release tag falls back +# to `openhuman@`). +OPENHUMAN_BUILD_SHA= # [optional] Default: true — set to false to disable anonymized analytics & crash reports OPENHUMAN_ANALYTICS_ENABLED=true diff --git a/.github/workflows/release-packages.yml b/.github/workflows/release-packages.yml index e4f97f5b8..0dfe4c0b0 100644 --- a/.github/workflows/release-packages.yml +++ b/.github/workflows/release-packages.yml @@ -60,10 +60,29 @@ jobs: sudo apt-get install -y --no-install-recommends \ pkg-config libssl-dev build-essential cmake + - name: Verify Sentry DSN is present + shell: bash + env: + OPENHUMAN_SENTRY_DSN: ${{ vars.OPENHUMAN_SENTRY_DSN }} + run: | + # Sentry DSN is baked into the binary at compile time via + # `option_env!`. Missing DSN here means the arm64 CLI silently + # ships without error reporting — fail the job instead. + if [ -z "${OPENHUMAN_SENTRY_DSN}" ]; then + echo "::error::vars.OPENHUMAN_SENTRY_DSN is empty — the Linux arm64 CLI would ship without error reporting." + echo "Configure the repository / environment variable before re-running the release." + exit 1 + fi + echo "OPENHUMAN_SENTRY_DSN is set (length=${#OPENHUMAN_SENTRY_DSN})" + - name: Build CLI binary and package tarball env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} OPENHUMAN_SENTRY_DSN: ${{ vars.OPENHUMAN_SENTRY_DSN }} + # Sentry release tracking (#405): keep the arm64 CLI tag in sync + # with the desktop build (`openhuman@+`). + OPENHUMAN_BUILD_SHA: ${{ github.sha }} + OPENHUMAN_APP_ENV: production run: | cargo build --release --bin openhuman-core VERSION="${{ github.event.release.tag_name }}" diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index f63f74f83..82770d739 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -456,6 +456,14 @@ jobs: # OAuth guardrails (#365): block openhuman://oauth/success on outdated desktop builds. VITE_MINIMUM_SUPPORTED_APP_VERSION: ${{ vars.VITE_MINIMUM_SUPPORTED_APP_VERSION }} VITE_LATEST_APP_DOWNLOAD_URL: ${{ vars.VITE_LATEST_APP_DOWNLOAD_URL }} + # Sentry release tracking (#405): baked into the bundle so every event + # groups under the same release, and @sentry/vite-plugin uploads the + # matching source maps when SENTRY_AUTH_TOKEN is present. + VITE_BUILD_SHA: ${{ needs.prepare-build.outputs.sha }} + SENTRY_RELEASE: openhuman@${{ needs.prepare-build.outputs.version }}+${{ needs.prepare-build.outputs.sha }} + SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }} + SENTRY_ORG: ${{ vars.SENTRY_ORG }} + SENTRY_PROJECT: ${{ vars.SENTRY_PROJECT_FRONTEND }} - name: Resolve core manifest and binary names id: core-paths @@ -498,6 +506,10 @@ jobs: CORE_BIN_NAME: ${{ steps.core-paths.outputs.core_bin_name }} OPENHUMAN_APP_ENV: ${{ inputs.build_target == 'staging' && 'staging' || 'production' }} OPENHUMAN_SENTRY_DSN: ${{ vars.OPENHUMAN_SENTRY_DSN }} + # Sentry release tracking (#405): `option_env!("OPENHUMAN_BUILD_SHA")` + # in src/main.rs bakes the short SHA into the release tag + # (`openhuman@+`) so core events match the frontend. + OPENHUMAN_BUILD_SHA: ${{ needs.prepare-build.outputs.sha }} - name: Stage sidecar for Tauri bundler shell: bash @@ -538,6 +550,20 @@ jobs: OPENHUMAN_APP_ENV: ${{ inputs.build_target == 'staging' && 'staging' || 'production' }} VITE_OPENHUMAN_APP_ENV: ${{ inputs.build_target == 'staging' && 'staging' || 'production' }} VITE_BACKEND_URL: ${{ needs.prepare-build.outputs.base_url }} + # Re-declare Vite env so tauri.conf.json's `beforeBuildCommand` + # (`vite build`) bakes Sentry + debug flags into the final bundle. + # The earlier explicit `yarn build` step ran with these, but the + # rebuild triggered here would strip them and ship a DSN-less UI. + VITE_SENTRY_DSN: ${{ vars.VITE_SENTRY_DSN }} + VITE_DEBUG: ${{ vars.VITE_DEBUG }} + # Sentry release tracking (#405) — must match the first `yarn build` + # so source maps uploaded there still resolve against the final bundle + # produced by the tauri-driven rebuild. + VITE_BUILD_SHA: ${{ needs.prepare-build.outputs.sha }} + SENTRY_RELEASE: openhuman@${{ needs.prepare-build.outputs.version }}+${{ needs.prepare-build.outputs.sha }} + SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }} + SENTRY_ORG: ${{ vars.SENTRY_ORG }} + SENTRY_PROJECT: ${{ vars.SENTRY_PROJECT_FRONTEND }} MACOSX_DEPLOYMENT_TARGET: ${{ matrix.settings.platform == 'macos-latest' && '10.15' || '' }} TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD || secrets.UPDATER_PRIVATE_KEY_PASSWORD }} TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY || secrets.UPDATER_PRIVATE_KEY }} diff --git a/app/.env.example b/app/.env.example index d95ca3d21..521c30525 100644 --- a/app/.env.example +++ b/app/.env.example @@ -22,6 +22,20 @@ VITE_SKILLS_GITHUB_REPO=tinyhumansai/openhuman-skills # [optional] Sentry DSN for error reporting (leave blank to disable) VITE_SENTRY_DSN= +# [optional] Short git SHA baked into the frontend bundle for the canonical +# Sentry release tag `openhuman@+`. CI sets this automatically +# from `needs.prepare-build.outputs.sha`; leave blank locally (release tag +# falls back to `openhuman@`). +VITE_BUILD_SHA= + +# [CI-only] Sentry source-map upload — set on CI to enable +# `@sentry/vite-plugin`. Leave blank locally; the plugin skips when +# `SENTRY_AUTH_TOKEN` is empty. +# SENTRY_AUTH_TOKEN= +# SENTRY_ORG= +# SENTRY_PROJECT= +# SENTRY_RELEASE= + # [optional] Dev-only: auto-inject JWT token to skip login flow VITE_DEV_JWT_TOKEN= diff --git a/app/package.json b/app/package.json index 2b2013bc9..cf93a4d16 100644 --- a/app/package.json +++ b/app/package.json @@ -96,6 +96,7 @@ "@types/redux-logger": "^3.0.13", "@typescript-eslint/eslint-plugin": "^8.54.0", "@typescript-eslint/parser": "^8.54.0", + "@sentry/vite-plugin": "^2.22.6", "@vitejs/plugin-react": "^4.6.0", "@vitest/coverage-v8": "^4.0.18", "@wdio/appium-service": "^9.24.0", diff --git a/app/src/services/analytics.ts b/app/src/services/analytics.ts index efcba2add..823de3f92 100644 --- a/app/src/services/analytics.ts +++ b/app/src/services/analytics.ts @@ -21,7 +21,7 @@ import * as Sentry from '@sentry/react'; import { getCoreStateSnapshot } from '../lib/coreState/store'; -import { IS_DEV, SENTRY_DSN } from '../utils/config'; +import { APP_ENVIRONMENT, IS_DEV, SENTRY_DSN, SENTRY_RELEASE } from '../utils/config'; import { enqueueError, registerSentrySender, type SanitizedSentryEvent } from './errorReportQueue'; // --------------------------------------------------------------------------- @@ -87,7 +87,12 @@ export function initSentry(): void { Sentry.init({ dsn: SENTRY_DSN, - environment: IS_DEV ? 'development' : 'production', + environment: APP_ENVIRONMENT, + // Canonical release tag shared with the core sidecar and source-map + // upload (see @sentry/vite-plugin in app/vite.config.ts). Lets events + // from all surfaces group under a single Sentry release with + // symbolicated stack traces. + release: SENTRY_RELEASE, enabled: !IS_DEV, // disable in dev builds // ----------------------------------------------------------------------- diff --git a/app/src/utils/config.ts b/app/src/utils/config.ts index 38595b27d..d817daa44 100644 --- a/app/src/utils/config.ts +++ b/app/src/utils/config.ts @@ -53,6 +53,35 @@ export const DEV_JWT_TOKEN = import.meta.env.DEV export const APP_VERSION = packageJson.version; +/** + * Deployment environment reported to Sentry and other observability surfaces. + * + * Derived from `VITE_OPENHUMAN_APP_ENV` (set by CI for production / staging + * bundles). Falls back to `development` in non-production builds so local + * debugging never mingles with real user events. + */ +export const APP_ENVIRONMENT: 'production' | 'staging' | 'development' = IS_DEV + ? 'development' + : APP_ENV === 'staging' + ? 'staging' + : 'production'; + +/** Short git SHA baked in at build time (`VITE_BUILD_SHA`). Empty locally. */ +export const BUILD_SHA = ((import.meta.env.VITE_BUILD_SHA as string | undefined) ?? '') + .trim() + .slice(0, 12); + +/** + * Canonical Sentry release identifier: `openhuman@[+]`. + * + * Matches the tag the Rust core sidecar reports (see `src/main.rs`) so events + * from the frontend, the core, and source-map uploads all group under the + * same release in the Sentry dashboard. + */ +export const SENTRY_RELEASE = BUILD_SHA + ? `openhuman@${APP_VERSION}+${BUILD_SHA}` + : `openhuman@${APP_VERSION}`; + /** * Minimum **desktop app** semver required for OAuth deep-link completion (`openhuman://oauth/success`). * diff --git a/app/src/vite-env.d.ts b/app/src/vite-env.d.ts index 03939c91f..a713957a5 100644 --- a/app/src/vite-env.d.ts +++ b/app/src/vite-env.d.ts @@ -6,6 +6,7 @@ interface ImportMetaEnv { readonly VITE_BACKEND_URL?: string; readonly VITE_SKILLS_GITHUB_REPO?: string; readonly VITE_SENTRY_DSN?: string; + readonly VITE_BUILD_SHA?: string; readonly VITE_DEV_JWT_TOKEN?: string; readonly VITE_DEV_FORCE_ONBOARDING?: string; readonly DEV: boolean; diff --git a/app/vite.config.ts b/app/vite.config.ts index 6ac4bdb10..29e8f76d4 100644 --- a/app/vite.config.ts +++ b/app/vite.config.ts @@ -1,10 +1,57 @@ -import { defineConfig } from "vite"; +import { defineConfig, type PluginOption } from "vite"; import react from "@vitejs/plugin-react"; +import { sentryVitePlugin } from "@sentry/vite-plugin"; + +import { readFileSync } from "node:fs"; +import { dirname, resolve } from "node:path"; +import { fileURLToPath } from "node:url"; import { nodePolyfills } from "vite-plugin-node-polyfills"; const host = process.env.TAURI_DEV_HOST; +const __dirname = dirname(fileURLToPath(import.meta.url)); +const pkg = JSON.parse( + readFileSync(resolve(__dirname, "package.json"), "utf8"), +) as { version: string }; + +// Canonical Sentry release — must stay in sync with the string produced by +// `SENTRY_RELEASE` in app/src/utils/config.ts and the core sidecar's +// `sentry::init` in src/main.rs so events from every surface group together. +function computeSentryRelease(): string { + const raw = (process.env.SENTRY_RELEASE ?? "").trim(); + if (raw) return raw; + const sha = (process.env.VITE_BUILD_SHA ?? "").trim().slice(0, 12); + return sha + ? `openhuman@${pkg.version}+${sha}` + : `openhuman@${pkg.version}`; +} + +// Gate source-map upload on the presence of SENTRY_AUTH_TOKEN so local dev +// and CI jobs that don't ship to users skip the plugin silently. The +// companion `SENTRY_ORG` / `SENTRY_PROJECT` come from CI env. +function maybeSentryPlugin(): PluginOption | null { + const authToken = process.env.SENTRY_AUTH_TOKEN; + if (!authToken) return null; + return sentryVitePlugin({ + authToken, + org: process.env.SENTRY_ORG, + project: process.env.SENTRY_PROJECT, + release: { name: computeSentryRelease() }, + sourcemaps: { + // Vite emits hashed asset files under dist/assets/ — upload every + // .js / .map the build produces. + assets: ["../dist/**/*.js", "../dist/**/*.map"], + // Never ship raw .map files to end users; the upload keeps a copy + // server-side for symbolication while the bundled app strips them. + filesToDeleteAfterUpload: ["../dist/**/*.map"], + }, + // Release tagging + commits are handled by sentry-cli / the plugin + // itself when AUTH_TOKEN and CI env (GITHUB_SHA etc.) are present. + telemetry: false, + }); +} + // https://vite.dev/config/ export default defineConfig(async () => ({ root: "src", @@ -12,6 +59,10 @@ export default defineConfig(async () => ({ build: { outDir: "../dist", emptyOutDir: true, + // Emit source maps so @sentry/vite-plugin can upload them; the plugin + // deletes the on-disk .map files after upload so users don't receive + // them in the shipped bundle. + sourcemap: true, }, plugins: [ nodePolyfills({ @@ -23,7 +74,8 @@ export default defineConfig(async () => ({ }, }), react(), - ], + maybeSentryPlugin(), + ].filter(Boolean) as PluginOption[], // Vite options tailored for Tauri development and only applied in `tauri dev` or `tauri build` // diff --git a/docs/sentry.md b/docs/sentry.md new file mode 100644 index 000000000..f847f8750 --- /dev/null +++ b/docs/sentry.md @@ -0,0 +1,142 @@ +# Sentry Release Tracking & Source Maps + +_Tracks issue [#405](https://github.com/tinyhumansai/openhuman/issues/405)._ + +OpenHuman reports crashes and errors from two surfaces that must group under +a **single Sentry release** so a new deploy's regressions are easy to see: + +- **Frontend** — `@sentry/react` in `app/src/services/analytics.ts`. +- **Rust core sidecar** — `sentry::init` in `src/main.rs`. + +The Tauri shell binary (`app/src-tauri`) has no Sentry wiring today. + +## Canonical release identifier + +Both surfaces report the **same** release tag: + +``` +openhuman@+ +``` + +Where: + +- `` is `packageJson.version` / `env!("CARGO_PKG_VERSION")`. +- `` is the first 12 chars of the commit that produced the + build. When the SHA is absent (local dev), the tag collapses to + `openhuman@` with no `+` suffix. + +The frontend computes this in `app/src/utils/config.ts::SENTRY_RELEASE` +from `VITE_BUILD_SHA`. The core does the same in +`src/main.rs::build_release_tag()` from `option_env!("OPENHUMAN_BUILD_SHA")`. + +## Environments + +Reported as the Sentry `environment` tag: + +| Value | When | +| ------------- | --------------------------------------------------------------- | +| `development` | Local `yarn tauri dev` / debug builds | +| `staging` | `VITE_OPENHUMAN_APP_ENV=staging` or `OPENHUMAN_APP_ENV=staging` | +| `production` | Release builds from `workflow_dispatch` with `build_target=production` | + +Fallback precedence for the core: + +1. `OPENHUMAN_APP_ENV` env var at runtime (override). +2. Compile-time `debug_assertions` → `development`. +3. Otherwise → `production`. + +## Source-map upload + +The frontend emits source maps (`vite.config.ts` sets `build.sourcemap = +true`). When `SENTRY_AUTH_TOKEN` is present at build time +`@sentry/vite-plugin`: + +1. Uploads every `dist/**/*.js` and its `.map` sibling. +2. Tags the upload with the canonical release name above. +3. **Deletes the on-disk `.map` files after upload** so users never receive + them in the shipped bundle. + +If `SENTRY_AUTH_TOKEN` is empty (local dev, smoke CI, forks without +secrets), the plugin registers as a no-op — the build still produces source +maps on disk but nothing is uploaded. This keeps the local dev loop zero- +config. + +## CI configuration + +`release.yml` + `release-packages.yml` thread the following through to the +build steps. Any subset can be set on a per-environment basis in the +`Production` / `Staging` GitHub Actions environment: + +### Required for upload to work + +| Name | Type | Scope | Purpose | +| ------------------------------------- | -------- | --------------- | --------------------------------------------- | +| `secrets.SENTRY_AUTH_TOKEN` | secret | build-desktop | Auth for `@sentry/vite-plugin` uploads | +| `vars.SENTRY_ORG` | variable | build-desktop | Sentry org slug | +| `vars.SENTRY_PROJECT_FRONTEND` | variable | build-desktop | Sentry project slug for the frontend bundle | +| `vars.OPENHUMAN_SENTRY_DSN` | variable | build-desktop | Core sidecar DSN (baked via `option_env!`) | +| `vars.VITE_SENTRY_DSN` | variable | build-desktop | Frontend DSN (baked by Vite define) | + +### Provided automatically + +| Name | Source | +| ------------------------ | ------------------------------------------------ | +| `VITE_BUILD_SHA` | `needs.prepare-build.outputs.sha` (tag commit) | +| `OPENHUMAN_BUILD_SHA` | Same — passed to `cargo build` for the sidecar | +| `SENTRY_RELEASE` | `openhuman@+` — same on both steps | + +### Personal Sentry DSN (local) + +Drop the DSN into your repo-local `.env`: + +```sh +# .env +OPENHUMAN_SENTRY_DSN=https://@o.ingest.sentry.io/ +``` + +`src/main.rs` now loads `.env` **before** `sentry::init`, so the runtime +env var is visible to the client at startup without needing a manual +`source scripts/load-dotenv.sh`. + +For the frontend, put `VITE_SENTRY_DSN` in `app/.env.local`. + +## Verification runbook + +1. **Event arrives**. Trigger a test event from the core CLI: + ```sh + ./target/release/openhuman-core sentry-test + # or on an installed release (Windows): + # "%LOCALAPPDATA%\Programs\OpenHuman\OpenHuman.exe" core sentry-test + # or (macOS): + # /Applications/OpenHuman.app/Contents/MacOS/openhuman-core-* sentry-test + ``` + The command prints an event UUID on success; search it in the Sentry + dashboard. +2. **Release tag is right**. On the event detail page, the `Release` field + should read `openhuman@+` (matching the tag that cut + the release). +3. **Environment tag is right**. Production CI dispatch → `production`. + Staging dispatch → `staging`. Local `yarn tauri dev` → `development`. +4. **Stack traces are symbolicated**. Force a frontend error from the + installed app; the event's stack trace should show original + TypeScript file names and line numbers (not hashed `assets/index-*.js`). +5. **CI failure is loud when misconfigured**. If `SENTRY_AUTH_TOKEN` is + missing and the release is supposed to upload source maps, the CI run + will warn in the Vite build log rather than silently producing an + un-symbolicated release. + +## Troubleshooting + +- **Events arrive without a release tag** — check the Vite build log for + `SENTRY_RELEASE`; if empty, the CI workflow didn't pass it through. +- **Events arrive without symbolication** — open the release in Sentry → + "Source Maps" tab. Missing artifacts mean either `SENTRY_AUTH_TOKEN` was + empty, or the plugin ran but the `assets:` glob didn't match (inspect the + upload summary printed during `yarn build`). +- **Frontend and core show different releases** — verify + `needs.prepare-build.outputs.sha` is identical between the core build + step (`OPENHUMAN_BUILD_SHA`) and the frontend build step + (`VITE_BUILD_SHA` / `SENTRY_RELEASE`). +- **No events from a release build, only from local** — `vars.*` probably + isn't defined on the `Production` environment. Set it and re-cut the + release. diff --git a/src/core/cli.rs b/src/core/cli.rs index 5d4e97d09..5314d86b2 100644 --- a/src/core/cli.rs +++ b/src/core/cli.rs @@ -76,11 +76,106 @@ pub fn run_from_cli_args(args: &[String]) -> Result<()> { ); crate::core::agent_cli::run_agent_command(&args[1..]) } + "sentry-test" => run_sentry_test_command(&args[1..]), // Generic namespace dispatcher: `openhuman ...` namespace => run_namespace_command(namespace, &args[1..], &grouped), } } +/// Handles the `sentry-test` subcommand used to verify Sentry wiring end-to-end. +/// +/// Captures an Error-level event against the currently initialized Sentry +/// client (see `sentry::init` in the binary entry point), flushes the client, +/// and prints the event UUID to stdout. Optional `--panic` flag additionally +/// triggers a panic so the panic integration is exercised too. +/// +/// Requires a DSN resolvable at runtime — either via the `OPENHUMAN_SENTRY_DSN` +/// env var or baked into the binary at build time via `option_env!`. Absent a +/// DSN, the command exits non-zero with a diagnostic instead of silently +/// producing no telemetry. +fn run_sentry_test_command(args: &[String]) -> Result<()> { + let mut message: Option = None; + let mut do_panic = false; + let mut i = 0usize; + + while i < args.len() { + match args[i].as_str() { + "--message" => { + message = Some( + args.get(i + 1) + .ok_or_else(|| anyhow::anyhow!("missing value for --message"))? + .clone(), + ); + i += 2; + } + "--panic" => { + do_panic = true; + i += 1; + } + "-h" | "--help" => { + println!("Usage: openhuman sentry-test [--message ] [--panic]"); + println!(); + println!(" --message Body of the Error-level event sent to Sentry"); + println!(" (default: \"openhuman sentry-test ping\")"); + println!(" --panic After capturing the event, trigger a panic so the"); + println!(" panic integration reports it as a separate event."); + println!(); + println!("Requires OPENHUMAN_SENTRY_DSN at runtime, or baked into the binary at"); + println!( + "build time via option_env!. On success, prints the event UUID to stdout." + ); + return Ok(()); + } + other => return Err(anyhow::anyhow!("unknown sentry-test arg: {other}")), + } + } + + let client = sentry::Hub::current().client(); + let dsn_host = client + .as_deref() + .and_then(|c| c.dsn()) + .map(|d| d.host().to_string()); + + match &dsn_host { + Some(host) => eprintln!("[sentry-test] Sentry client active (dsn host: {host})"), + None => { + return Err(anyhow::anyhow!( + "Sentry is not initialized in this binary — no DSN is resolvable. \ + Set OPENHUMAN_SENTRY_DSN in the environment (or rebuild with it defined \ + at compile time) and try again." + )); + } + } + + let msg = message.unwrap_or_else(|| "openhuman sentry-test ping".to_string()); + + sentry::configure_scope(|scope| { + scope.set_tag("test", "true"); + scope.set_tag("source", "sentry-test-cli"); + }); + + let event_id = sentry::capture_message(&msg, sentry::Level::Error); + + if let Some(c) = client { + if !c.flush(Some(std::time::Duration::from_secs(5))) { + eprintln!( + "[sentry-test] WARNING: flush timed out after 5s — event may not have reached Sentry." + ); + } + } + + println!("{event_id}"); + + if do_panic { + eprintln!( + "[sentry-test] Triggering panic as requested — the panic integration should capture it." + ); + panic!("openhuman sentry-test intentional panic"); + } + + Ok(()) +} + /// Loads key/value pairs from a `.env` file into the process environment. /// /// This is used for all CLI entrypoints so direct namespace commands pick up @@ -515,6 +610,7 @@ fn print_general_help(grouped: &BTreeMap>) { println!(" openhuman agent [options] (inspect agent definitions & prompts)"); println!(" openhuman voice [--hotkey ] [--mode ] (voice dictation server)"); println!(" openhuman tree-summarizer [options] (summary tree CLI)"); + println!(" openhuman sentry-test [--message ] [--panic] (verify Sentry wiring)"); println!(" openhuman [--param value ...]\n"); println!("Available namespaces:"); for namespace in grouped.keys() { diff --git a/src/main.rs b/src/main.rs index dbe49f8e7..bd66bc96e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -14,6 +14,15 @@ use regex::Regex; /// information is redacted before being sent to the server. After setup, it /// delegates execution to the core library based on CLI arguments. fn main() { + // Load `.env` before `sentry::init` so a DSN defined only in the dotenv + // file is visible to the Sentry client at startup. `dotenvy::dotenv()` is + // a no-op for variables already present in the process environment, and + // the CLI dispatcher later calls `load_dotenv_for_cli` which honors + // `OPENHUMAN_DOTENV_PATH`; this early call handles the common default + // case (repo-local `.env`) so startup-time consumers (Sentry, config + // overrides) see the same values as runtime RPC handlers. + let _ = dotenvy::dotenv(); + // Initialize Sentry as the very first operation so the guard outlives everything. // If OPENHUMAN_SENTRY_DSN is unset or empty, sentry::init returns a no-op guard. let _sentry_guard = sentry::init(sentry::ClientOptions { @@ -23,12 +32,8 @@ fn main() { .or_else(|| option_env!("OPENHUMAN_SENTRY_DSN").map(|s| s.to_string())) .filter(|s| !s.is_empty()) .and_then(|s| s.parse().ok()), - release: Some(std::borrow::Cow::Borrowed(env!("CARGO_PKG_VERSION"))), - environment: Some(if cfg!(debug_assertions) { - "development".into() - } else { - "production".into() - }), + release: Some(std::borrow::Cow::Owned(build_release_tag())), + environment: Some(std::borrow::Cow::Owned(resolve_environment())), send_default_pii: false, before_send: Some(std::sync::Arc::new(|mut event| { // Strip server_name (hostname) to avoid leaking machine identity @@ -57,6 +62,46 @@ fn main() { } } +// --------------------------------------------------------------------------- +// Release / environment resolution for Sentry +// --------------------------------------------------------------------------- + +/// Canonical release tag: `openhuman@[+]`. +/// +/// Matches the string the frontend reports (`SENTRY_RELEASE` in +/// `app/src/utils/config.ts`) so events from every surface group under +/// the same release in the Sentry dashboard and benefit from the same +/// source-map upload. +fn build_release_tag() -> String { + let version = env!("CARGO_PKG_VERSION"); + let sha = option_env!("OPENHUMAN_BUILD_SHA").unwrap_or("").trim(); + let sha_short: String = sha.chars().take(12).collect(); + if sha_short.is_empty() { + format!("openhuman@{version}") + } else { + format!("openhuman@{version}+{sha_short}") + } +} + +/// Resolve the deployment environment reported to Sentry. +/// +/// Honors `OPENHUMAN_APP_ENV` at runtime (`staging` / `production`) so the +/// same binary could in principle be redeployed between environments; falls +/// back to debug/release detection when unset. +fn resolve_environment() -> String { + if let Ok(value) = std::env::var("OPENHUMAN_APP_ENV") { + let trimmed = value.trim().to_ascii_lowercase(); + if !trimmed.is_empty() { + return trimmed; + } + } + if cfg!(debug_assertions) { + "development".to_string() + } else { + "production".to_string() + } +} + // --------------------------------------------------------------------------- // Secret scrubbing // --------------------------------------------------------------------------- diff --git a/yarn.lock b/yarn.lock index 008748d12..4c95077d0 100644 --- a/yarn.lock +++ b/yarn.lock @@ -67,7 +67,7 @@ resolved "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.6.tgz" integrity sha512-2lfu57JtzctfIrcGMz992hyLlByuzgIk58+hhGCxjKZ3rWI82NnVLjXcaTqkI2NvlcvOskZaiZ5kjUALo3Lpxg== -"@babel/core@^7.24.4": +"@babel/core@^7.18.5", "@babel/core@^7.24.4": version "7.29.0" resolved "https://registry.npmjs.org/@babel/core/-/core-7.29.0.tgz" integrity sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA== @@ -898,7 +898,7 @@ resolved "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz" integrity sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw== -"@jridgewell/sourcemap-codec@^1.4.14", "@jridgewell/sourcemap-codec@^1.5.0", "@jridgewell/sourcemap-codec@^1.5.5": +"@jridgewell/sourcemap-codec@^1.4.14", "@jridgewell/sourcemap-codec@^1.4.15", "@jridgewell/sourcemap-codec@^1.5.0", "@jridgewell/sourcemap-codec@^1.5.5": version "1.5.5" resolved "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz" integrity sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og== @@ -1412,6 +1412,11 @@ "@sentry-internal/browser-utils" "10.38.0" "@sentry/core" "10.38.0" +"@sentry/babel-plugin-component-annotate@2.23.1": + version "2.23.1" + resolved "https://registry.yarnpkg.com/@sentry/babel-plugin-component-annotate/-/babel-plugin-component-annotate-2.23.1.tgz#c3c87cf8ed776390c7cc9a3e4a14342debf7a793" + integrity sha512-l1z8AvI6k9I+2z49OgvP3SlzB1M0Lw24KtceiJibNaSyQwxsItoT9/XftZ/8BBtkosVmNOTQhL1eUsSkuSv1LA== + "@sentry/browser@10.38.0": version "10.38.0" resolved "https://registry.npmjs.org/@sentry/browser/-/browser-10.38.0.tgz" @@ -1423,6 +1428,74 @@ "@sentry-internal/replay-canvas" "10.38.0" "@sentry/core" "10.38.0" +"@sentry/bundler-plugin-core@2.23.1": + version "2.23.1" + resolved "https://registry.yarnpkg.com/@sentry/bundler-plugin-core/-/bundler-plugin-core-2.23.1.tgz#74423d58159e709b59770bd870f068f171a7b470" + integrity sha512-JA6utNiwMKv6Jfj0Hmk0DI/XUizSHg7HhhkFETKhRlYEhZAdkyz1atDBg0ncKNgRBKyHeSYWcMFtUyo26VB76w== + dependencies: + "@babel/core" "^7.18.5" + "@sentry/babel-plugin-component-annotate" "2.23.1" + "@sentry/cli" "2.39.1" + dotenv "^16.3.1" + find-up "^5.0.0" + glob "^9.3.2" + magic-string "0.30.8" + unplugin "1.0.1" + +"@sentry/cli-darwin@2.39.1": + version "2.39.1" + resolved "https://registry.yarnpkg.com/@sentry/cli-darwin/-/cli-darwin-2.39.1.tgz#75c338a53834b4cf72f57599f4c72ffb36cf0781" + integrity sha512-kiNGNSAkg46LNGatfNH5tfsmI/kCAaPA62KQuFZloZiemTNzhy9/6NJP8HZ/GxGs8GDMxic6wNrV9CkVEgFLJQ== + +"@sentry/cli-linux-arm64@2.39.1": + version "2.39.1" + resolved "https://registry.yarnpkg.com/@sentry/cli-linux-arm64/-/cli-linux-arm64-2.39.1.tgz#27db44700c33fcb1e8966257020b43f8494373e6" + integrity sha512-5VbVJDatolDrWOgaffsEM7znjs0cR8bHt9Bq0mStM3tBolgAeSDHE89NgHggfZR+DJ2VWOy4vgCwkObrUD6NQw== + +"@sentry/cli-linux-arm@2.39.1": + version "2.39.1" + resolved "https://registry.yarnpkg.com/@sentry/cli-linux-arm/-/cli-linux-arm-2.39.1.tgz#451683fa9a5a60b1359d104ec71334ed16f4b63c" + integrity sha512-DkENbxyRxUrfLnJLXTA4s5UL/GoctU5Cm4ER1eB7XN7p9WsamFJd/yf2KpltkjEyiTuplv0yAbdjl1KX3vKmEQ== + +"@sentry/cli-linux-i686@2.39.1": + version "2.39.1" + resolved "https://registry.yarnpkg.com/@sentry/cli-linux-i686/-/cli-linux-i686-2.39.1.tgz#9965a81f97a94e8b6d1d15589e43fee158e35201" + integrity sha512-pXWVoKXCRrY7N8vc9H7mETiV9ZCz+zSnX65JQCzZxgYrayQPJTc+NPRnZTdYdk5RlAupXaFicBI2GwOCRqVRkg== + +"@sentry/cli-linux-x64@2.39.1": + version "2.39.1" + resolved "https://registry.yarnpkg.com/@sentry/cli-linux-x64/-/cli-linux-x64-2.39.1.tgz#31fe008b02f92769543dc9919e2a5cbc4cda7889" + integrity sha512-IwayNZy+it7FWG4M9LayyUmG1a/8kT9+/IEm67sT5+7dkMIMcpmHDqL8rWcPojOXuTKaOBBjkVdNMBTXy0mXlA== + +"@sentry/cli-win32-i686@2.39.1": + version "2.39.1" + resolved "https://registry.yarnpkg.com/@sentry/cli-win32-i686/-/cli-win32-i686-2.39.1.tgz#609e8790c49414011445e397130560c777850b35" + integrity sha512-NglnNoqHSmE+Dz/wHeIVRnV2bLMx7tIn3IQ8vXGO5HWA2f8zYJGktbkLq1Lg23PaQmeZLPGlja3gBQfZYSG10Q== + +"@sentry/cli-win32-x64@2.39.1": + version "2.39.1" + resolved "https://registry.yarnpkg.com/@sentry/cli-win32-x64/-/cli-win32-x64-2.39.1.tgz#1a874a5570c6d162b35d9d001c96e5389d07d2cb" + integrity sha512-xv0R2CMf/X1Fte3cMWie1NXuHmUyQPDBfCyIt6k6RPFPxAYUgcqgMPznYwVMwWEA1W43PaOkSn3d8ZylsDaETw== + +"@sentry/cli@2.39.1": + version "2.39.1" + resolved "https://registry.yarnpkg.com/@sentry/cli/-/cli-2.39.1.tgz#916bb5b7567ccf7fdf94ef6cf8a2b9ab78370d29" + integrity sha512-JIb3e9vh0+OmQ0KxmexMXg9oZsR/G7HMwxt5BUIKAXZ9m17Xll4ETXTRnRUBT3sf7EpNGAmlQk1xEmVN9pYZYQ== + dependencies: + 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" + optionalDependencies: + "@sentry/cli-darwin" "2.39.1" + "@sentry/cli-linux-arm" "2.39.1" + "@sentry/cli-linux-arm64" "2.39.1" + "@sentry/cli-linux-i686" "2.39.1" + "@sentry/cli-linux-x64" "2.39.1" + "@sentry/cli-win32-i686" "2.39.1" + "@sentry/cli-win32-x64" "2.39.1" + "@sentry/core@10.38.0": version "10.38.0" resolved "https://registry.npmjs.org/@sentry/core/-/core-10.38.0.tgz" @@ -1436,6 +1509,14 @@ "@sentry/browser" "10.38.0" "@sentry/core" "10.38.0" +"@sentry/vite-plugin@^2.22.6": + version "2.23.1" + resolved "https://registry.yarnpkg.com/@sentry/vite-plugin/-/vite-plugin-2.23.1.tgz#352bc2e2349f6a7f6c704f4f105382c4d33083e0" + integrity sha512-avtjtIQ019sZW3FklpmNNsQOnYZjCHpnVxgDGElfZb+AaR4AvtHNlxXLJp+iqEfSK+Xok8MJarJqIgCaWcF40Q== + dependencies: + "@sentry/bundler-plugin-core" "2.23.1" + unplugin "1.0.1" + "@sinclair/typebox@^0.34.0": version "0.34.48" resolved "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.48.tgz" @@ -2313,6 +2394,18 @@ acorn@^8.15.0: resolved "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz" integrity sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg== +acorn@^8.8.1: + version "8.16.0" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.16.0.tgz#4ce79c89be40afe7afe8f3adb902a1f1ce9ac08a" + integrity sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw== + +agent-base@6: + version "6.0.2" + resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-6.0.2.tgz#49fff58577cfee3f37176feab4c22e00f86d7f77" + integrity sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ== + dependencies: + debug "4" + agent-base@^7.1.0, agent-base@^7.1.2: version "7.1.4" resolved "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz" @@ -3483,6 +3576,11 @@ domutils@^3.0.1, domutils@^3.2.2: domelementtype "^2.3.0" domhandler "^5.0.3" +dotenv@^16.3.1: + version "16.6.1" + resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.6.1.tgz#773f0e69527a8315c7285d5ee73c4459d20a8020" + integrity sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow== + dotenv@^17.2.0: version "17.2.4" resolved "https://registry.npmjs.org/dotenv/-/dotenv-17.2.4.tgz" @@ -4461,6 +4559,16 @@ glob@^8.1.0: minimatch "^5.0.1" once "^1.3.0" +glob@^9.3.2: + version "9.3.5" + resolved "https://registry.yarnpkg.com/glob/-/glob-9.3.5.tgz#ca2ed8ca452781a3009685607fdf025a899dfe21" + integrity sha512-e1LleDykUz2Iu+MTYdkSsuWX8lvAjAcs0Xef0lNIu0S2wOAzuTxCJtcd9S3cijlwYF18EsU3rzb8jPVobxDh9Q== + dependencies: + fs.realpath "^1.0.0" + minimatch "^8.0.2" + minipass "^4.2.4" + path-scurry "^1.6.1" + globals@^14.0.0: version "14.0.0" resolved "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz" @@ -4671,6 +4779,14 @@ https-browserify@^1.0.0: resolved "https://registry.npmjs.org/https-browserify/-/https-browserify-1.0.0.tgz" integrity sha512-J+FkSdyD+0mA0N+81tMotaRMfSL9SGi+xpD3T6YApKsc3bGSXJlfXri3VyFOeYkfLRQisDk1W+jIFFKBeUBbBg== +https-proxy-agent@^5.0.0: + version "5.0.1" + resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz#c59ef224a04fe8b754f3db0063a25ea30d0005d6" + integrity sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA== + dependencies: + agent-base "6" + debug "4" + https-proxy-agent@^7.0.6: version "7.0.6" resolved "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz" @@ -5520,6 +5636,13 @@ lz-string@^1.5.0: resolved "https://registry.npmjs.org/lz-string/-/lz-string-1.5.0.tgz" integrity sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ== +magic-string@0.30.8: + version "0.30.8" + resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.30.8.tgz#14e8624246d2bedba70d5462aa99ac9681844613" + integrity sha512-ISQTe55T2ao7XtlAStud6qwYPZjE4GK1S/BeVPus4jrq6JuOnQ00YKQC581RWhR122W7msZV263KzVeLoqidyQ== + dependencies: + "@jridgewell/sourcemap-codec" "^1.4.15" + magic-string@^0.30.12, magic-string@^0.30.21, magic-string@^0.30.3: version "0.30.21" resolved "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz" @@ -5921,6 +6044,13 @@ minimatch@^5.0.1, minimatch@^5.1.0, minimatch@^5.1.6: dependencies: brace-expansion "^2.0.1" +minimatch@^8.0.2: + version "8.0.7" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-8.0.7.tgz#954766e22da88a3e0a17ad93b58c15c9d8a579de" + integrity sha512-V+1uQNdzybxa14e/p00HZnQNNcTjnRJjDxg2V8wtkjFctq4M7hXFws4oekyTP0Jebeq7QYtpFyOeBAjc88zvYg== + dependencies: + brace-expansion "^2.0.1" + minimatch@^9.0.0, minimatch@^9.0.4, minimatch@^9.0.5: version "9.0.5" resolved "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz" @@ -5933,6 +6063,11 @@ minimist@^1.2.0, minimist@^1.2.6, minimist@^1.2.8: resolved "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz" integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA== +minipass@^4.2.4: + version "4.2.8" + resolved "https://registry.yarnpkg.com/minipass/-/minipass-4.2.8.tgz#f0010f64393ecfc1d1ccb5f582bcaf45f48e1a3a" + integrity sha512-fNzuVyifolSLFL4NzpF+wEF4qrgqaaKX0haXPQEdQ7NKAN+WecoKMHV09YcuL/DHxrUsYQOK3MiuDf7Ip2OXfQ== + "minipass@^5.0.0 || ^6.0.2 || ^7.0.0", minipass@^7.1.2: version "7.1.2" resolved "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz" @@ -6008,6 +6143,13 @@ netmask@^2.0.2: resolved "https://registry.npmjs.org/netmask/-/netmask-2.0.2.tgz" integrity sha512-dBpDMdxv9Irdq66304OLfEmQ9tbNRFnFTuZiLo+bD+r332bBmMJ8GBLXklIXXgxd3+v9+KUnZaUR5PJMa75Gsg== +node-fetch@^2.6.7: + version "2.7.0" + resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.7.0.tgz#d0f0fa6e3e2dc1d27efcd8ad99d550bda94d187d" + integrity sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A== + dependencies: + whatwg-url "^5.0.0" + node-releases@^2.0.27: version "2.0.27" resolved "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz" @@ -6433,7 +6575,7 @@ path-parse@^1.0.7: resolved "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz" integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== -path-scurry@^1.11.1: +path-scurry@^1.11.1, path-scurry@^1.6.1: version "1.11.1" resolved "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz" integrity sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA== @@ -7814,6 +7956,11 @@ tr46@^6.0.0: dependencies: punycode "^2.3.1" +tr46@~0.0.3: + version "0.0.3" + resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a" + integrity sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw== + tree-kill@^1.2.2: version "1.2.2" resolved "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz" @@ -8032,6 +8179,16 @@ unist-util-visit@^5.0.0: unist-util-is "^6.0.0" unist-util-visit-parents "^6.0.0" +unplugin@1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/unplugin/-/unplugin-1.0.1.tgz#83b528b981cdcea1cad422a12cd02e695195ef3f" + integrity sha512-aqrHaVBWW1JVKBHmGo33T5TxeL0qWzfvjWokObHA9bYmN7eNDkwOxmLjhioHl9878qDFMAaT51XNroRyuz7WxA== + dependencies: + acorn "^8.8.1" + chokidar "^3.5.3" + webpack-sources "^3.2.3" + webpack-virtual-modules "^0.5.0" + update-browserslist-db@^1.2.0: version "1.2.3" resolved "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz" @@ -8239,11 +8396,26 @@ webdriverio@9.24.0: urlpattern-polyfill "^10.0.0" webdriver "9.24.0" +webidl-conversions@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871" + integrity sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ== + webidl-conversions@^8.0.1: version "8.0.1" resolved "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-8.0.1.tgz" integrity sha512-BMhLD/Sw+GbJC21C/UgyaZX41nPt8bUTg+jWyDeg7e7YN4xOM05YPSIXceACnXVtqyEw/LMClUQMtMZ+PGGpqQ== +webpack-sources@^3.2.3: + version "3.3.4" + resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-3.3.4.tgz#a338b95eb484ecc75fbb196cbe8a2890618b4891" + integrity sha512-7tP1PdV4vF+lYPnkMR0jMY5/la2ub5Fc/8VQrrU+lXkiM6C4TjVfGw7iKfyhnTQOsD+6Q/iKw0eFciziRgD58Q== + +webpack-virtual-modules@^0.5.0: + version "0.5.0" + resolved "https://registry.yarnpkg.com/webpack-virtual-modules/-/webpack-virtual-modules-0.5.0.tgz#362f14738a56dae107937ab98ea7062e8bdd3b6c" + integrity sha512-kyDivFZ7ZM0BVOUteVbDFhlRt7Ah/CSPwJdi8hBpkK7QLumUqdLtVfm/PX/hkcnrvr0i77fO5+TjZ94Pe+C9iw== + whatwg-encoding@^3.1.1: version "3.1.1" resolved "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz" @@ -8270,6 +8442,14 @@ whatwg-url@^16.0.0: tr46 "^6.0.0" webidl-conversions "^8.0.1" +whatwg-url@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-5.0.0.tgz#966454e8765462e37644d3626f6742ce8b70965d" + integrity sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw== + dependencies: + tr46 "~0.0.3" + webidl-conversions "^3.0.0" + which-boxed-primitive@^1.1.0, which-boxed-primitive@^1.1.1: version "1.1.1" resolved "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.1.1.tgz"