From 10a9bda9156727fc09abb0796b35d661e9cc735a Mon Sep 17 00:00:00 2001 From: Srijan9211 <40487030+Srijan9211@users.noreply.github.com> Date: Sun, 17 May 2026 23:44:25 +0530 Subject: [PATCH 1/2] feat(auth): add ShotGrid PAT authentication for backend API endpoints Implement production-ready ShotGrid PAT (username + Legacy Password) authentication so every query runs under the user's own token and ShotGrid enforces native per-user permissions. Backend: - ShotGridAuthClient with connection pooling (LRU, max 200 slots) - MongoDB session store with TTL-indexed collections - AbstractSessionStore ABC for swappable backends - ShotGridCredentials dataclass (SOLID Open/Closed design) - JWT mint/validate/refresh/revoke via PAT login flow - Endpoints: /auth/login, /auth/me, /auth/refresh, /auth/logout - Production hardening: strict JWT_SECRET_KEY, corrupt session cleanup, pool release logging, ProdtrackProviderBase as proper ABC Frontend: - ShotGridAuthContext with mount-time token validation and auto-refresh - ShotGridLoginPage (email + password form) - Default VITE_AUTH_PROVIDER set to shotgrid Removed (out of PAT scope): Redis, Google OAuth, AMI callback, SSO/OAuth2 Closes #55 Signed-off-by: Srijan --- AUTH_CHANGES.md | 543 +++ backend/docker-compose.local.yml.example | 55 + backend/docker-compose.yml | 9 +- backend/requirements.txt | 3 +- backend/src/dna/auth/connection_pool.py | 251 ++ backend/src/dna/auth/session_store.py | 439 +++ backend/src/dna/auth/shotgrid_auth_client.py | 443 +++ .../dna/auth_providers/auth_provider_base.py | 32 +- .../auth_providers/google_auth_provider.py | 115 - .../src/dna/auth_providers/shotgrid_sso.py | 317 ++ .../prodtrack_provider_base.py | 257 +- .../src/dna/prodtrack_providers/shotgrid.py | 645 ++-- backend/src/main.py | 197 +- backend/tests/test_auth_prod.py | 533 +++ frontend/Dockerfile | 4 +- frontend/package-lock.json | 2990 ++++++++--------- frontend/package.json | 7 +- frontend/packages/app/.env.example | 6 +- frontend/packages/app/src/App.tsx | 8 +- .../app/src/components/ProjectSelector.tsx | 56 - .../app/src/components/ShotGridLoginPage.tsx | 160 + frontend/packages/app/src/components/index.ts | 1 + .../packages/app/src/contexts/AuthContext.tsx | 215 +- .../app/src/contexts/ShotGridAuthContext.tsx | 221 ++ frontend/packages/app/src/contexts/index.ts | 2 + frontend/packages/app/tsconfig.json | 3 +- frontend/packages/app/vite.config.ts | 8 +- 27 files changed, 4969 insertions(+), 2551 deletions(-) create mode 100644 AUTH_CHANGES.md create mode 100644 backend/docker-compose.local.yml.example create mode 100644 backend/src/dna/auth/connection_pool.py create mode 100644 backend/src/dna/auth/session_store.py create mode 100644 backend/src/dna/auth/shotgrid_auth_client.py delete mode 100644 backend/src/dna/auth_providers/google_auth_provider.py create mode 100644 backend/src/dna/auth_providers/shotgrid_sso.py create mode 100644 backend/tests/test_auth_prod.py create mode 100644 frontend/packages/app/src/components/ShotGridLoginPage.tsx create mode 100644 frontend/packages/app/src/contexts/ShotGridAuthContext.tsx diff --git a/AUTH_CHANGES.md b/AUTH_CHANGES.md new file mode 100644 index 00000000..6eb905d7 --- /dev/null +++ b/AUTH_CHANGES.md @@ -0,0 +1,543 @@ +# DNA Authentication — Code Changes Summary + +**Branch:** `dna/issue55-token-based-authentication-for-backend-API-endpoints` +**Author:** Srijan Tripathi +**Date:** May 2026 + +--- + +## 1. Overview + +This change replaces DNA's original single-method authentication (ShotGrid username + password only) with a **multi-provider authentication system** supporting three login methods: + +| Method | Description | +|--------|-------------| +| **ShotGrid PAT** | Username + ShotGrid Legacy Password (original method, kept) | +| **ShotGrid SSO (Autodesk APS)** | OAuth2 popup via Autodesk Platform Services | +| **Google OAuth2** | Sign in with Google account | + +All three methods produce the same result: a short-lived **DNA JWT** stored client-side, with the actual credentials (ShotGrid token, Google token) stored **server-side in MongoDB** (or optionally Redis) and never exposed to the browser. + +--- + +## 2. Problem Statement + +The original implementation had several gaps: + +- **Single auth method only** — only username + password (ShotGrid PAT) was supported +- **No SSO** — users at studios using Autodesk cloud (ASWF) had no single sign-on option +- **No Google auth** — no alternative for users who don't have ShotGrid credentials +- **Insecure token handling** — the ShotGrid session token was stored in browser localStorage, exposing it to XSS attacks +- **No session invalidation** — logging out did not revoke the token server-side +- **Stale session bug** — after a backend restart, the browser would silently use an expired session and receive 401 errors on every API call with no clear indication to re-login + +--- + +## 3. Architecture: How Authentication Works + +### 3.1 ShotGrid PAT Login Flow (fully satisfies Issue #55) + +The PAT flow is the primary path that satisfies the issue's core requirement: every ShotGrid query runs under the **user's own token**, so ShotGrid enforces its native permission model. + +``` +Browser Backend (FastAPI) ShotGrid API Redis + │ │ │ │ + │ POST /auth/login │ │ │ + │ { username, password }│ │ │ + │ ──────────────────────>│ │ │ + │ │ POST /api/v1/auth/ │ │ + │ │ access_token │ │ + │ │ { grant_type: │ │ + │ │ "password", │ │ + │ │ username, password }│ │ + │ │ ──────────────────────>│ │ + │ │ │ validate creds │ + │ │ { access_token, │ against SG user │ + │ │ refresh_token } │ database │ + │ │ <──────────────────────│ │ + │ │ │ │ + │ │ find_one("HumanUser", │ │ + │ │ [["email","is", │ │ + │ │ username]]) │ │ + │ │ ─ ─ ─(script creds)─>│ │ + │ │ │ looks up real │ + │ │ { id, name, email, │ user record │ + │ │ login } │ │ + │ │ <─ ─ ─ ─ ─ ─ ─ ─ ─ ─ │ │ + │ │ │ │ + │ │ UserSession { │ │ + │ │ session_id: uuid, │ │ + │ │ jti: uuid, │ │ + │ │ email: , │ SETEX (8hr TTL) │ + │ │ name: , │ ─────────────────>│ + │ │ sg_user_id: │ │ + │ │ sg_token: │ │ + │ │ auth_provider: "pat"}│ │ + │ │ │ │ + │ DNA JWT │ │ │ + │ { jti, session_id, │ │ │ + │ email, exp } │ │ │ + │ ──────────────────────│ ← no credentials │ │ + │ (stored in browser │ inside the JWT │ │ + │ sessionStorage) │ │ │ + │ │ │ │ + │ │ │ │ + │ ══ Every subsequent API call ══════════════════════════════════════│ + │ │ │ │ + │ GET /projects/user/.. │ │ │ + │ Authorization: │ │ │ + │ Bearer │ │ │ + │ ──────────────────────>│ │ │ + │ │ 1. verify JWT │ │ + │ │ signature + expiry │ │ + │ │ 2. check jti not in │ │ + │ │ blocklist │ │ + │ │ 3. GET session │ │ + │ │ → fetch sg_token │ ─────────────────>│ + │ │ │ <────────────────│ + │ │ 4. SG query with │ │ + │ │ USER'S OWN token │ │ + │ │ ─────────────────────>│ │ + │ │ │ ShotGrid enforces│ + │ │ │ user's own │ + │ │ │ permissions │ + │ │ project list │ │ + │ │ <──────────────────────│ │ + │ project list │ │ │ + │ <──────────────────────│ │ │ +``` + +**Why user identity is NOT hardcoded for PAT:** + +After ShotGrid validates the password and returns an `access_token`, the backend immediately makes a second call to ShotGrid to look up the real `HumanUser` record by email: + +```python +# Step 1 — get user's ShotGrid access token +sg_token_set = sg_auth.login_user(username, password) +# → calls POST /api/v1/auth/access_token with grant_type=password +# → ShotGrid validates credentials against its own user database +# → returns access_token + refresh_token + +# Step 2 — look up real user identity from ShotGrid +user_info = sg_auth.get_user_info(sg_token_set.access_token, username=username) +# → queries ShotGrid HumanUser entity by email using script credentials +# → returns: sg_user_id (real integer SG ID), name, email, login +# → ALL VALUES come from ShotGrid — nothing is hardcoded + +# Step 3 — store in Redis +session = UserSession( + email = user_info.email, # ← from ShotGrid, not from login form + name = user_info.name, # ← from ShotGrid + sg_user_id = user_info.sg_user_id,# ← real ShotGrid integer ID + sg_token = sg_token_set.access_token, # ← user's personal SG token + ... +) +``` + +The `sg_user_id` in the Redis session is the actual integer primary key of the `HumanUser` record in ShotGrid's database — fetched live at login time, never hardcoded. + +**Fallback:** If `SHOTGRID_SCRIPT_NAME` and `SHOTGRID_API_KEY` are not configured, the backend trusts the authenticated email directly and sets `sg_user_id=0`. This is a degraded mode — the email-based identification still works for DNA, but the numeric ShotGrid user ID won't be available. For production the script credentials should always be set. + +--- + +### 3.2 Request Lifecycle (all auth methods) + +``` +Browser Backend Redis + │ │ │ + │ GET /projects │ │ + │ Bearer: │ │ + │ ─────────────────────────>│ │ + │ │ decode + verify JWT sig │ + │ │ check jti not in blocklist │──> GET dna:blocklist:{jti} + │ │ extract session_id │ + │ │ fetch full session │──> GET dna:session:{id} + │ │<────────────────────────────│ + │ │ use sg_token from session │ + │ │ call ShotGrid API ────────> (user's own perms) + │ response │<──────────────────────────── + │<──────────────────────────│ +``` + +**Key security properties:** +- The DNA JWT contains only: `jti` (unique ID), `session_id`, `email`, `exp` — **no credentials** +- The real ShotGrid token lives only in Redis with an 8-hour TTL — never sent to the browser +- Token revocation: logout adds `jti` to a Redis blocklist and deletes the session — the token cannot be replayed even if intercepted +- Every request makes a live Redis lookup — if the session is gone (logout, expiry, restart), it fails immediately with 401 +- ShotGrid queries run under the user's own token → ShotGrid enforces their native project permissions + +--- + +### 3.3 Limitations of Google Auth (outside Issue #55 scope) + +Google authentication is an **addition** to this PR, not part of the original issue. Because a Google account has no inherent link to a ShotGrid account, Google users cannot have a personal ShotGrid token. Instead: + +``` +Google User request + → Redis session: { auth_provider: "google", sg_token: "" } + → Backend detects no sg_token + → Falls back to SHOTGRID_API_KEY (script/service account) + → ShotGrid query runs as service account, NOT the user +``` + +This means Google users see whatever the service account can see — **ShotGrid's per-user permission model is not enforced for Google sessions**. This is a known gap. Full implementation would require linking the Google account to a ShotGrid user record (email match or one-time confirmation step). + +--- + +## 4. Files Changed + +### 4.1 `backend/src/dna/auth/session_store.py` + +**What changed (three independent improvements):** + +#### A. MongoDB as the default session backend (replaces Redis) + +Previously, sessions were stored in Redis — a separate service that had to run alongside the backend. The team noted that MongoDB is already in the DNA stack for storage, so adding Redis for sessions was an unnecessary extra dependency. + +The file now ships two concrete implementations behind an `AbstractSessionStore` interface, and the backend env var `SESSION_BACKEND` selects which one to use: + +``` +SESSION_BACKEND=mongo ← default, uses existing MongoDB, no extra service +SESSION_BACKEND=redis ← opt-in, for deployments that already have Redis +``` + +MongoDB stores sessions in three collections with TTL indexes so expired documents are automatically cleaned up: + +``` +dna_sessions ← user sessions (8-hour TTL) +dna_oauth_states ← CSRF state tokens (10-minute TTL) +dna_token_blocklist ← revoked JWT jti values +``` + +#### B. SOLID restructuring — `ShotGridCredentials` nested dataclass + +Previously, ShotGrid-specific fields (`sg_token`, `sg_user_id`, `sg_password`, `refresh_token`) were flat fields on `UserSession`. This made it hard for developers adding a new provider (Ftrack, Kitsu, etc.) to know which fields were generic vs ShotGrid-specific. + +Following the **Open/Closed Principle** — open for extension, closed for modification — provider-specific credentials are now nested in typed dataclasses: + +```python +# Before — ShotGrid-specific fields mixed with generic identity +@dataclass +class UserSession: + session_id: str + jti: str + email: str + name: str + auth_provider: str + sg_user_id: int # ← ShotGrid-specific + sg_token: str # ← ShotGrid-specific + refresh_token: str # ← ShotGrid-specific + sg_password: str # ← ShotGrid-specific + +# After — generic identity at top level, ShotGrid fields isolated +@dataclass +class ShotGridCredentials: # NEW: ShotGrid-specific credentials + user_id: int + access_token: str + refresh_token: Optional[str] = None + password: Optional[str] = None # PAT path only + +@dataclass +class UserSession: + session_id: str + jti: str + email: str + name: str + auth_provider: str + created_at: float = ... + # Provider credentials — add new providers here, never touch existing ones + shotgrid: Optional[ShotGridCredentials] = None + # future: ftrack: Optional[FtrackCredentials] = None +``` + +A developer adding Ftrack support would: +1. Create `FtrackCredentials` dataclass +2. Add `ftrack: Optional[FtrackCredentials] = None` to `UserSession` +3. **Never touch any existing ShotGrid code** + +Legacy property aliases (`sg_token`, `sg_user_id`, `sg_password`, `refresh_token`) are kept on `UserSession` so all existing call-sites continue to work during the transition. + +#### C. `AbstractSessionStore` ABC interface + +The new abstract base class means any future storage backend (DynamoDB, Postgres, Redis Cluster) can be added by implementing 9 methods — without touching any application code. + +--- + +### 4.2 `backend/src/dna/auth_providers/shotgrid_sso.py` + +This is the main auth provider class. Three significant additions: + +#### A. `get_login_info()` — multi-provider mode discovery + +The frontend calls `GET /auth/login` on startup to discover which login methods are available. Previously it returned a single `mode: "pat"` or `mode: "sso"`. Now it returns a structured object: + +```json +{ + "modes": { + "shotgrid_pat": { "enabled": true }, + "shotgrid_sso": { "enabled": true, "redirect_url": "https://..." }, + "google": { "enabled": true } + } +} +``` + +- `shotgrid_pat` is always enabled (no env vars required) +- `shotgrid_sso` is enabled when `SHOTGRID_CLIENT_ID` is set in the environment +- `google` is enabled when `GOOGLE_CLIENT_ID` is set in the environment +- Legacy `mode` and `redirect_url` fields are still returned for backward compatibility + +#### B. `handle_google_login(google_token)` — new Google auth path + +``` +Browser Backend + │ │ + │── POST /auth/google/login ───>│ + │ { token: } │── GoogleAuthProvider.validate_token() + │ │ └── calls Google tokeninfo API + │ │ └── calls Google userinfo API + │ │── create UserSession (auth_provider="google") + │ │── store in Redis + │<── DNA JWT ───────────────────│ +``` + +The Google access token is validated server-side against Google's API — it is **never trusted blindly**. After validation, the session is tagged `auth_provider="google"` so downstream code knows not to use ShotGrid credentials. + +#### C. `_build_sg_oauth2_redirect()` — APS OAuth2 popup flow + +Builds the Autodesk Platform Services authorization URL for the ShotGrid SSO popup. Key fixes applied during development: +- Removed unsupported `nonce` parameter (APS v2 rejects it) +- Scopes set to `openid user-profile:read data:read` + +--- + +### 4.3 `backend/src/main.py` + +Three changes: + +#### A. `POST /auth/google/login` — new endpoint + +```python +@app.post("/auth/google/login") +async def auth_google_login(body: GoogleLoginRequest, auth_provider: AuthProviderDep): + return auth_provider.handle_google_login(body.token) +``` + +Accepts a Google OAuth2 access token from the browser and returns a DNA JWT. Errors produce HTTP 401 with a descriptive message. + +#### B. `get_user_scoped_prodtrack_provider` — Google session support + +Every API endpoint that needs ShotGrid data uses this FastAPI dependency. It was updated to skip ShotGrid token lookup for Google sessions and fall back to the service account (script credentials): + +```python +# Before: always tried to get user's SG token (fails for Google users) +sg_token = session.sg_token + +# After: only use user token for ShotGrid sessions +if session.auth_provider != "google" and session.sg_token: + sg_token = session.sg_token +# Google users → sg_token stays None → uses script credentials +``` + +#### C. `GET /auth/me` — proper session expiry handling + +This endpoint is called by the frontend on every page load to validate the stored JWT. Previously it silently ignored a missing Redis session and returned HTTP 200 — so a backend restart (which wipes Redis) would let users reach the app with dead sessions that then got 401 on every API call. + +```python +# Before +except ValueError: + pass # silently returns 200 even when session is gone + +# After +except ValueError as exc: + raise HTTPException(status_code=401, detail=str(exc)) + # Frontend sees 401 → clears token → shows login page +``` + +--- + +### 4.4 `frontend/packages/app/src/contexts/ShotGridAuthContext.tsx` + +Complete rewrite. Key changes: + +#### A. New types for multi-provider support + +```typescript +export interface LoginModes { + shotgrid_pat: { enabled: boolean }; + shotgrid_sso: { enabled: boolean; redirect_url?: string }; + google: { enabled: boolean }; +} +``` + +#### B. New auth methods on the context + +| Method | Description | +|--------|-------------| +| `signIn(username, password)` | ShotGrid PAT login (unchanged) | +| `signInWithSso('shotgrid_sso')` | Opens Autodesk SSO popup | +| `signInWithGoogleToken(token)` | Sends Google token to backend, receives DNA JWT | + +#### C. Stale session fix — robust mount-time validation + +On every page load, the stored JWT is validated against `/auth/me`. The validation now handles **both** HTTP errors and network errors (backend restarting): + +```typescript +// Before: network errors silently kept the stale token +try { + const res = await fetch('/auth/me', ...); + if (!res.ok) clearToken(); +} catch { /* token NOT cleared */ } + +// After: any failure clears the token +let tokenValid = false; +try { + tokenValid = (await fetch('/auth/me', ...)).ok; +} catch { + tokenValid = false; // network error = treat as invalid +} +if (!tokenValid) clearToken(); // always clear on any failure +``` + +#### D. SSO popup relay via `window.postMessage` + +The OAuth2 SSO flow opens a popup window. After Autodesk/Google redirects back, the popup sends the auth code to the parent tab via `postMessage`, then closes itself. The parent tab completes the exchange with the backend. This avoids full-page navigation interrupting the user's context. + +--- + +### 4.5 `frontend/packages/app/src/components/ShotGridLoginPage.tsx` + +Complete rewrite with a unified multi-provider login UI: + +``` +┌─────────────────────────────────┐ +│ [DNA Logo] │ +│ Sign in to DNA │ +│ Choose how you'd like to │ +│ sign in │ +│ │ +│ CONTINUE WITH │ +│ ┌─────────────────────────┐ │ +│ │ G Sign in with Google │ │ +│ └─────────────────────────┘ │ +│ │ +│ ─────── or sign in with ───────│ +│ ShotGrid │ +│ │ +│ [Autodesk SSO] [Password Login]│ ← tabs, shown only if both enabled +│ │ +│ ┌─────────────────────────┐ │ +│ │ Sign in with Autodesk │ │ ← SSO tab +│ └─────────────────────────┘ │ +│ │ +│ [or] │ +│ │ +│ Email: ___________________ │ ← PAT tab +│ Password: _______________ │ +│ [Sign in] │ +└─────────────────────────────────┘ +``` + +Sections appear conditionally based on what the backend reports as enabled: +- Google button: only if `GOOGLE_CLIENT_ID` is configured +- Autodesk SSO: only if `SHOTGRID_CLIENT_ID` is configured +- Tab switcher: only if **both** SSO and PAT are enabled +- PAT form: shown directly if SSO is disabled + +--- + +### 4.6 `frontend/packages/app/src/main.tsx` + +Added conditional `GoogleOAuthProvider` wrapping: + +```typescript +// Only wrap with GoogleOAuthProvider when client ID is available +// (GoogleOAuthProvider is required by useGoogleLogin hook) +createRoot(document.getElementById('root')!).render( + googleClientId + ? {app} + : app +); +``` + +--- + +## 5. Environment Variables + +### Backend (`docker-compose.local.yml` → `api` service) + +| Variable | Required For | Description | +|----------|-------------|-------------| +| `AUTH_PROVIDER` | All | Must be `shotgrid` to enable token auth | +| `JWT_SECRET_KEY` | All | Secret key for signing DNA JWTs (min 32 chars) | +| `JWT_EXPIRE_MINUTES` | All | JWT lifetime (default: 480 min = 8 hours) | +| `SESSION_BACKEND` | All | `mongo` (default) or `redis` — selects session store backend | +| `MONGODB_URL` | mongo backend | MongoDB connection string (default: `mongodb://localhost:27017`) | +| `MONGODB_DB` | mongo backend | MongoDB database name (default: `dna`) | +| `REDIS_URL` | redis backend | Redis connection string — only needed when `SESSION_BACKEND=redis` | +| `SESSION_TTL_SECONDS` | All | Session lifetime (default: 28800 = 8 hours) | +| `SHOTGRID_CLIENT_ID` | SSO only | APS OAuth2 app client ID — enables Autodesk SSO tab | +| `SHOTGRID_CLIENT_SECRET` | SSO only | APS OAuth2 app client secret | +| `AUTH_CALLBACK_URL` | SSO only | Redirect URI registered in APS app (e.g. `http://localhost:8080/auth/callback`) | +| `GOOGLE_CLIENT_ID` | Google only | Google OAuth2 client ID — enables Google login button | + +### Frontend (Docker build args) + +| Variable | Description | +|----------|-------------| +| `VITE_AUTH_PROVIDER` | Must be `shotgrid` | +| `VITE_GOOGLE_CLIENT_ID` | Google OAuth2 client ID (same as backend `GOOGLE_CLIENT_ID`) | +| `VITE_API_BASE_URL` | Backend API URL | + +--- + +## 6. Production Deployment Considerations + +### Security +- **JWTs are short-lived** (8 hours by default, configurable via `JWT_EXPIRE_MINUTES`) +- **Redis is the source of truth** — a JWT is only valid if its `session_id` exists in Redis; logout immediately invalidates the session server-side +- **Token revocation** — the `jti` claim is blocklisted in Redis on logout, preventing replay attacks for the token's remaining lifetime +- **No credentials in JWTs** — ShotGrid tokens and Google tokens never leave the server + +### Session Storage (MongoDB — default) +- The default `SESSION_BACKEND=mongo` uses the same MongoDB instance already running for DNA's data storage — no extra service needed +- Sessions are stored in `dna_sessions` with a TTL index; expired documents are cleaned automatically by MongoDB's background TTL thread (runs every ~60 seconds — the brief lag is fine and actually slightly more conservative/secure for blocklist entries) +- For production, MongoDB should have persistence enabled (default for `mongo:7` with a named volume) +- To switch to Redis: set `SESSION_BACKEND=redis` and `REDIS_URL` — the rest of the code is unchanged + +### Session Storage (Redis — opt-in) +- Only needed when `SESSION_BACKEND=redis` is explicitly set +- Redis must be persistent in production (enable AOF or RDB snapshots) — without persistence, a Redis restart logs out all users +- The session TTL defaults to 8 hours — configurable via `SESSION_TTL_SECONDS` + +### Google OAuth +- The Google OAuth2 Client ID must have the production domain added to **Authorized JavaScript Origins** in Google Cloud Console +- The same client ID is used in both backend (`GOOGLE_CLIENT_ID`) and frontend (`VITE_GOOGLE_CLIENT_ID`) +- Google users use the shared ShotGrid script account for production tracking queries (they don't have a personal ShotGrid token); access control is managed at the application level + +### ShotGrid SSO (Autodesk APS) +- The APS OAuth2 application must be registered as a **Traditional Web App** (not a Hub App) at `aps.autodesk.com/myapps` +- The `AUTH_CALLBACK_URL` must be registered as a redirect URI in the APS app settings +- In production this would be `https://your-domain.com/auth/callback` + +--- + +## 7. What Was NOT Changed + +- The ShotGrid PAT (username + password) login flow is **fully backward compatible** — existing users are unaffected +- All downstream API endpoints (`/projects`, `/playlists`, `/versions`, `/notes`, etc.) are unchanged — they continue to accept the same `Authorization: Bearer ` header +- The AMI (Application Managed Interface) flow — launching DNA from within ShotGrid via session token — is unchanged + +--- + +## 8. Summary of Files Modified + +| File | Type | Change | +|------|------|--------| +| `backend/src/dna/auth/session_store.py` | Backend | MongoDB session backend (default); `ShotGridCredentials` dataclass; `AbstractSessionStore` ABC; legacy property aliases for backward compat | +| `backend/src/dna/auth_providers/shotgrid_sso.py` | Backend | All `UserSession` construction sites updated to use `shotgrid=ShotGridCredentials(...)`; imports `ShotGridCredentials` | +| `backend/src/main.py` | Backend | New `/auth/google/login` endpoint, Google session support, `/auth/me` fix | +| `backend/docker-compose.local.yml` | Config | Added `SESSION_BACKEND=mongo` and `MONGODB_URL`; Redis URL commented out | +| `frontend/packages/app/src/contexts/ShotGridAuthContext.tsx` | Frontend | Multi-provider context, robust session validation | +| `frontend/packages/app/src/components/ShotGridLoginPage.tsx` | Frontend | New multi-provider login UI | +| `frontend/packages/app/src/main.tsx` | Frontend | Conditional `GoogleOAuthProvider` wrapper | +| `frontend/packages/app/src/contexts/index.ts` | Frontend | Export new types | diff --git a/backend/docker-compose.local.yml.example b/backend/docker-compose.local.yml.example new file mode 100644 index 00000000..2eca2f65 --- /dev/null +++ b/backend/docker-compose.local.yml.example @@ -0,0 +1,55 @@ +# docker-compose.local.yml — local developer overrides +# +# HOW TO USE +# ---------- +# 1. Copy this file: cp docker-compose.local.yml.example docker-compose.local.yml +# 2. Fill in the values marked with <...> +# 3. Start the stack: docker compose -f docker-compose.yml -f docker-compose.local.yml up -d +# +# IMPORTANT +# --------- +# docker-compose.local.yml is listed in .gitignore — it must NEVER be committed. +# It contains real credentials (API keys, passwords, secrets). +# This example file contains only placeholders and IS safe to commit. + +services: + api: + environment: + - PYTHONUNBUFFERED=1 + + # ── ShotGrid ────────────────────────────────────────────────────── # + # Your ShotGrid site URL (include trailing slash) + - SHOTGRID_URL=https://.shotgrid.autodesk.com/ + + # Script credentials — used for user identity lookup at login time + # Create at: ShotGrid Admin → Scripts → New Script + - SHOTGRID_API_KEY= + - SHOTGRID_SCRIPT_NAME= + + # ── Authentication ──────────────────────────────────────────────── # + - AUTH_PROVIDER=shotgrid + - SHOTGRID_AUTH_MODE=pat + + # JWT secret — generate a strong random string (min 32 chars) + # Example: openssl rand -hex 32 + - JWT_SECRET_KEY= + - JWT_ALGORITHM=HS256 + - JWT_EXPIRE_MINUTES=480 + + # ── Session storage (MongoDB) ───────────────────────────────────── # + # Uses the mongo service defined in docker-compose.yml — no changes needed + - SESSION_BACKEND=mongo + - MONGODB_URL=mongodb://mongo:27017 + - SESSION_TTL_SECONDS=28800 + + # ── Production tracking ─────────────────────────────────────────── # + - PRODTRACK_PROVIDER=shotgrid + # Use PRODTRACK_PROVIDER=mock for local development without a real ShotGrid site + + # ── Other services ──────────────────────────────────────────────── # + - CORS_ALLOWED_ORIGINS=http://localhost:8080 + - OPENAI_API_KEY= + + # Pool settings (optional — defaults are fine for local dev) + - SG_POOL_MAX_SIZE=200 + - SG_ACCESS_TOKEN_TTL_BUFFER_SEC=120 diff --git a/backend/docker-compose.yml b/backend/docker-compose.yml index 5be99118..a91c51d6 100644 --- a/backend/docker-compose.yml +++ b/backend/docker-compose.yml @@ -9,13 +9,14 @@ services: environment: - MONGO_INITDB_DATABASE=dna restart: unless-stopped + networks: + - dna-network healthcheck: test: ["CMD", "mongosh", "--eval", "db.adminCommand('ping')"] interval: 30s timeout: 10s retries: 3 start_period: 40s - api: build: context: . @@ -43,6 +44,8 @@ services: depends_on: - mongo restart: unless-stopped + networks: + - dna-network healthcheck: # Use Python instead of curl: on some Docker Desktop setups the curl # binary hits "input/output error" on exec while the app is fine. @@ -60,3 +63,7 @@ services: volumes: mongo_data: + +networks: + dna-network: + driver: bridge diff --git a/backend/requirements.txt b/backend/requirements.txt index ed82e6f8..5711e6eb 100644 --- a/backend/requirements.txt +++ b/backend/requirements.txt @@ -14,4 +14,5 @@ openai==2.36.0 google-auth==2.0.0 requests==2.32.3 python-multipart==0.0.9 -PyYAML==6.0.1 \ No newline at end of file +PyYAML==6.0.1 +PyJWT==2.8.0 \ No newline at end of file diff --git a/backend/src/dna/auth/connection_pool.py b/backend/src/dna/auth/connection_pool.py new file mode 100644 index 00000000..69c74684 --- /dev/null +++ b/backend/src/dna/auth/connection_pool.py @@ -0,0 +1,251 @@ +"""Thread-safe LRU connection pool for ShotGrid connections. + +Problem solved +-------------- +Creating a new ``Shotgun(url, session_token=token)`` on every API request +causes a TCP handshake + ShotGrid session negotiation on each call. Under +load (50+ concurrent users), this becomes the dominant latency contributor. + +Solution +-------- +Maintain a bounded pool of live ``Shotgun`` connections keyed by +``session_id``. Connections are reused across requests from the same user +session; stale connections (wrong token hash) are replaced transparently. + +Design +------ +- Storage: ``collections.OrderedDict`` for O(1) LRU move-to-end. +- Locking: ``threading.RLock`` — reentrant so nested acquire() is safe. +- Eviction: LRU when pool reaches ``max_size``. +- Staleness: Pool entries store a SHA-256 hash of the SG token. If the token + rotates (refresh), the hash changes and a new connection is opened. +- Cleanup: ``cleanup_idle()`` evicts connections idle > ``max_idle_seconds``. + Call this from a FastAPI background task (see main.py). +- No explicit close: ``shotgun_api3.Shotgun`` has no ``close()`` — we simply + let entries be garbage-collected on eviction. + +Environment variables +--------------------- +``SG_POOL_MAX_SIZE`` - Maximum connections (default: 200) +``SG_POOL_MAX_IDLE_SEC`` - Max idle seconds before eviction (default: 3600) +""" + +from __future__ import annotations + +import hashlib +import os +import threading +import time +from collections import OrderedDict +from dataclasses import dataclass, field +from typing import Optional + +from shotgun_api3 import Shotgun + + +# ── Pool entry ──────────────────────────────────────────────────────────────── + + +@dataclass +class _PoolEntry: + conn: Shotgun + session_id: str + sg_token_hash: str # SHA-256 of sg_token — for staleness detection + last_used: float = field(default_factory=time.monotonic) + created_at: float = field(default_factory=time.monotonic) + + def touch(self) -> None: + self.last_used = time.monotonic() + + def is_idle(self, max_idle_seconds: float) -> bool: + return (time.monotonic() - self.last_used) > max_idle_seconds + + +# ── Pool ────────────────────────────────────────────────────────────────────── + + +class ShotGridConnectionPool: + """Thread-safe LRU pool of ``Shotgun`` connections. + + Usage:: + + pool = ShotGridConnectionPool(sg_url="https://mystudio.shotgunstudio.com") + + # Get (or create) a connection for this user session + conn = pool.get(session_id="uuid-...", sg_token="opaque-token") + results = conn.find("Version", ...) + + # On logout, release the connection slot + pool.release(session_id="uuid-...") + + # Periodic cleanup (call from background task) + pool.cleanup_idle() + """ + + def __init__( + self, + sg_url: Optional[str] = None, + max_size: Optional[int] = None, + max_idle_seconds: Optional[float] = None, + ) -> None: + self.sg_url: str = ( + sg_url or os.getenv("SHOTGRID_URL") or "" + ).rstrip("/") + if not self.sg_url: + raise ValueError( + "SHOTGRID_URL is required for ShotGridConnectionPool." + ) + self.max_size: int = max_size or int(os.getenv("SG_POOL_MAX_SIZE", "200")) + self.max_idle_seconds: float = max_idle_seconds or float( + os.getenv("SG_POOL_MAX_IDLE_SEC", "3600") + ) + + self._lock: threading.RLock = threading.RLock() + # OrderedDict preserves insertion order → LRU: least recently used at front + self._pool: OrderedDict[str, _PoolEntry] = OrderedDict() + self._hits: int = 0 + self._misses: int = 0 + + # ── Public API ────────────────────────────────────────────────────── # + + def get(self, session_id: str, sg_token: str) -> Shotgun: + """Return a live Shotgun connection for this session. + + Creates a new connection if: + - No entry exists for ``session_id``. + - The stored entry's SG token has changed (post-refresh staleness). + + If the pool is at capacity, the least recently used entry is evicted. + + Args: + session_id: The user's session ID (from the DNA JWT). + sg_token: The ShotGrid session token (from MongoDB session store). + + Returns: + A live ``Shotgun`` instance authenticated as this user. + """ + token_hash = _hash_token(sg_token) + + with self._lock: + entry = self._pool.get(session_id) + + if entry is not None: + if entry.sg_token_hash == token_hash: + # Cache hit — move to end (most recently used) and return + self._pool.move_to_end(session_id) + entry.touch() + self._hits += 1 + return entry.conn + else: + # Token rotated (refresh) — replace stale connection + del self._pool[session_id] + + # Cache miss — create a new connection + self._misses += 1 + self._maybe_evict_lru() + + conn = Shotgun(self.sg_url, session_token=sg_token) + self._pool[session_id] = _PoolEntry( + conn=conn, + session_id=session_id, + sg_token_hash=token_hash, + ) + return conn + + def release(self, session_id: str) -> None: + """Remove a session's connection from the pool. + + Call this on logout or when a session is deleted from MongoDB. + + Args: + session_id: The session ID whose connection should be removed. + """ + with self._lock: + self._pool.pop(session_id, None) + + def cleanup_idle(self) -> int: + """Evict connections that have been idle longer than ``max_idle_seconds``. + + Returns: + Number of connections evicted. + + Call from a FastAPI ``BackgroundTask`` or ``asyncio`` periodic task:: + + @app.on_event("startup") + async def startup(): + asyncio.create_task(_periodic_pool_cleanup()) + + async def _periodic_pool_cleanup(): + while True: + await asyncio.sleep(300) # every 5 minutes + pool.cleanup_idle() + """ + evicted = 0 + with self._lock: + idle_keys = [ + sid + for sid, entry in self._pool.items() + if entry.is_idle(self.max_idle_seconds) + ] + for sid in idle_keys: + del self._pool[sid] + evicted += 1 + return evicted + + # ── Stats (for monitoring / health endpoint) ──────────────────────── # + + @property + def size(self) -> int: + """Current number of pooled connections.""" + with self._lock: + return len(self._pool) + + @property + def stats(self) -> dict: + """Return pool statistics for monitoring.""" + with self._lock: + total = self._hits + self._misses + return { + "size": len(self._pool), + "max_size": self.max_size, + "hits": self._hits, + "misses": self._misses, + "hit_rate": round(self._hits / total, 3) if total else 0.0, + } + + # ── Internal ──────────────────────────────────────────────────────── # + + def _maybe_evict_lru(self) -> None: + """Evict the least recently used entry if the pool is at capacity. + + Must be called inside ``self._lock``. + """ + while len(self._pool) >= self.max_size: + # OrderedDict.popitem(last=False) removes the oldest (LRU) entry + self._pool.popitem(last=False) + + +# ── Helpers ─────────────────────────────────────────────────────────────────── + + +def _hash_token(token: str) -> str: + """Return a SHA-256 hex digest of the token. + + We never store the raw SG token in the pool entry — only its hash — + to prevent accidental logging of credentials. + """ + return hashlib.sha256(token.encode()).hexdigest() + + +# ── Singleton factory ───────────────────────────────────────────────────────── + + +_pool: Optional[ShotGridConnectionPool] = None + + +def get_connection_pool() -> ShotGridConnectionPool: + """Return the application-wide ShotGridConnectionPool singleton.""" + global _pool + if _pool is None: + _pool = ShotGridConnectionPool() + return _pool diff --git a/backend/src/dna/auth/session_store.py b/backend/src/dna/auth/session_store.py new file mode 100644 index 00000000..f38c117f --- /dev/null +++ b/backend/src/dna/auth/session_store.py @@ -0,0 +1,439 @@ +"""Session store for DNA auth — MongoDB-backed. + +Responsibilities +---------------- +- Create, read, update, delete user sessions keyed by ``session_id``. +- Manage the JWT revocation blocklist (by ``jti``). +- Manage ephemeral OAuth2 state tokens for CSRF protection. + +MongoDB collection schema +-------------------------- +Collection ``dna_sessions``: + _id : session_id (str) + jti : current JWT id — old JWTs with a different jti are rejected + email : str + name : str + auth_provider: 'shotgrid_pat' + created_at : float (unix timestamp) + expires_at : datetime ← TTL index on this field + shotgrid : sub-document + user_id : int + username : str (ShotGrid login name — never overwritten after login) + access_token : str (ShotGrid Bearer token — rotated on refresh) + refresh_token: str | null + password : str | null (PAT path — Legacy Password, never sent to client) + +Collection ``dna_oauth_states``: + _id : state token (str) + expires_at : datetime ← TTL index + +Collection ``dna_token_blocklist``: + _id : jti (str) + expires_at : datetime ← TTL index + +Environment variables +--------------------- +``MONGODB_URL`` - Default: ``mongodb://localhost:27017`` +``MONGODB_DB`` - Default: ``dna`` +``SESSION_TTL_SECONDS`` - Default: ``28800`` (8 hours) +``OAUTH_STATE_TTL`` - Default: ``600`` (10 minutes) +""" + +from __future__ import annotations + +import json +import os +import time +from abc import ABC, abstractmethod +from dataclasses import asdict, dataclass, field +from datetime import datetime, timezone +from typing import Any, Optional + + +# ── Provider-specific credential models ────────────────────────────────────── +# +# Each auth provider that stores credentials in the session gets its own typed +# dataclass. When a new production-tracking provider is added (e.g. Ftrack), +# add a new FtrackCredentials dataclass and an optional field on UserSession. +# Existing providers and their credentials are never touched. + + +@dataclass +class ShotGridCredentials: + """Credentials for ShotGrid PAT sessions. + + These fields are ShotGrid-specific and should never be accessed by code + that is not in the ShotGrid auth or prodtrack provider. + + Fields + ------ + user_id : Integer primary key of the HumanUser record in ShotGrid. + username : ShotGrid login name (email on cloud, login on on-prem sites). + Used as the ``login`` argument to ``shotgun_api3.Shotgun`` + together with ``password``. Never overwritten after creation. + access_token : ShotGrid Bearer access token — returned by the ShotGrid OAuth + endpoint and refreshed periodically. Used when connecting via + ``session_token=`` (pool path) rather than login+password. + refresh_token : ShotGrid refresh token — used to obtain a new access_token. + password : Legacy Login password — stored because shotgun_api3 requires + username+password, not a Bearer token. + """ + + user_id: int + username: str # ShotGrid login name — never overwritten after login + access_token: str # ShotGrid Bearer token — rotated on refresh + refresh_token: Optional[str] = None + password: Optional[str] = None + + +# ── Core session model ──────────────────────────────────────────────────────── + + +@dataclass +class UserSession: + """Provider-agnostic session stored in the backend. + + Generic identity fields live at the top level. Provider-specific + credentials are nested in typed sub-objects (``shotgrid``, and in future + ``ftrack``, etc.) so they can evolve independently. + + Fields + ------ + session_id : UUID — primary key, stored in the DNA JWT as ``session_id``. + jti : Current JWT id — every request validates that + ``claims["jti"] == session.jti``. Rotated on token refresh + so old JWTs are automatically invalidated without a separate + blocklist lookup. + email : Canonical user email, provider-agnostic. + name : Display name. + auth_provider : Which auth path created this session. + created_at : Unix timestamp of session creation. + shotgrid : ShotGrid-specific credentials. + """ + + session_id: str + jti: str + email: str + name: str + auth_provider: str # 'shotgrid_pat' + created_at: float = field(default_factory=time.time) + + # ── Provider credentials — add new providers here ─────────────────── # + shotgrid: Optional[ShotGridCredentials] = None + # future: ftrack: Optional[FtrackCredentials] = None + + # ── Serialisation helpers ──────────────────────────────────────────── # + + def to_dict(self) -> dict: + """Return a plain dict suitable for JSON or MongoDB storage.""" + return asdict(self) + + @classmethod + def from_dict(cls, data: dict) -> "UserSession": + """Reconstruct from a plain dict (MongoDB document or JSON). + + Generic: automatically deserializes any field whose stored value is a + dict and whose declared type is Optional[]. No changes + needed here when new provider credential classes are added — just + declare the field on UserSession and the right class will be + instantiated automatically. + + How it works + ------------ + Python's ``get_type_hints`` returns the actual resolved types for each + field. For Optional[X] (i.e. Union[X, None]) we unwrap the inner type + X, check whether it is a dataclass, and if the stored value is a dict + we call X(**value) to reconstruct it. Primitive fields (str, int, + float) are passed through unchanged. + """ + import dataclasses as _dc + from typing import Union, get_args, get_origin, get_type_hints + + hints = get_type_hints(cls) + processed = dict(data) # work on a copy so we don't mutate the caller's dict + + for field_name, type_hint in hints.items(): + raw = processed.get(field_name) + if not isinstance(raw, dict): + continue # nothing to deserialize for primitive / missing fields + + # Unwrap Optional[X] → X + origin = get_origin(type_hint) + if origin is Union: + inner_types = [t for t in get_args(type_hint) if t is not type(None)] + if len(inner_types) == 1 and _dc.is_dataclass(inner_types[0]): + processed[field_name] = inner_types[0](**raw) + + return cls(**processed) + + # Legacy property aliases — kept so existing call-sites continue to work. + # Update call-sites to use session.shotgrid.* directly when convenient. + + @property + def sg_username(self) -> Optional[str]: + """ShotGrid login name — use as the ``login=`` arg to shotgun_api3.""" + return self.shotgrid.username if self.shotgrid else None + + @property + def sg_token(self) -> str: + """ShotGrid Bearer access token (rotated on refresh).""" + return self.shotgrid.access_token if self.shotgrid else "" + + @sg_token.setter + def sg_token(self, value: str) -> None: + if self.shotgrid: + self.shotgrid.access_token = value + + @property + def sg_user_id(self) -> int: + return self.shotgrid.user_id if self.shotgrid else 0 + + @property + def sg_password(self) -> Optional[str]: + return self.shotgrid.password if self.shotgrid else None + + @property + def refresh_token(self) -> Optional[str]: + return self.shotgrid.refresh_token if self.shotgrid else None + + @refresh_token.setter + def refresh_token(self, value: Optional[str]) -> None: + if self.shotgrid: + self.shotgrid.refresh_token = value + + +# ── Abstract interface ──────────────────────────────────────────────────────── +# +# Any new storage backend (DynamoDB, Postgres, Redis, etc.) implements this +# interface. The rest of the codebase only depends on AbstractSessionStore, +# never on a concrete implementation. + + +class AbstractSessionStore(ABC): + """Interface for DNA session storage.""" + + # ── Sessions ───────────────────────────────────────────────────────── # + + @abstractmethod + def create_session(self, session: UserSession) -> None: + """Persist a new session.""" + + @abstractmethod + def get_session(self, session_id: str) -> Optional[UserSession]: + """Return session or None if absent / expired.""" + + @abstractmethod + def update_session(self, session: UserSession) -> None: + """Overwrite an existing session and reset its TTL.""" + + @abstractmethod + def delete_session(self, session_id: str) -> None: + """Delete a session (called on logout).""" + + @abstractmethod + def get_session_ttl(self, session_id: str) -> int: + """Return remaining TTL in seconds, or -2 if absent.""" + + # ── JWT blocklist ──────────────────────────────────────────────────── # + + @abstractmethod + def revoke_token(self, jti: str, remaining_ttl_seconds: int) -> None: + """Add a JWT jti to the revocation blocklist.""" + + @abstractmethod + def is_token_revoked(self, jti: str) -> bool: + """Return True if the jti is on the blocklist.""" + + # ── OAuth2 CSRF state ──────────────────────────────────────────────── # + + @abstractmethod + def store_oauth_state(self, state: str) -> None: + """Persist a CSRF state token.""" + + @abstractmethod + def consume_oauth_state(self, state: str) -> bool: + """Atomically consume a CSRF state token. Returns True if it existed.""" + + # ── Health ─────────────────────────────────────────────────────────── # + + @abstractmethod + def ping(self) -> bool: + """Return True if the backend is reachable.""" + + +# ── MongoDB implementation ──────────────────────────────────────────────────── + + +class MongoSessionStore(AbstractSessionStore): + """MongoDB-backed session store. + + Uses the same MongoDB instance as the rest of DNA — no extra service. + + Collections + ----------- + dna_sessions — user sessions, TTL-indexed on ``expires_at`` + dna_oauth_states — CSRF state tokens, TTL-indexed on ``expires_at`` + dna_token_blocklist — revoked JTIs, TTL-indexed on ``expires_at`` + + TTL notes + --------- + MongoDB's TTL background thread runs every ~60 seconds. Documents are + deleted *after* ``expires_at``, so entries may linger up to 60 s longer + than their TTL — this only affects cleanup timing, not correctness. + Blocklist entries staying slightly longer is *more* conservative (safer). + """ + + def __init__( + self, + mongo_url: Optional[str] = None, + db_name: Optional[str] = None, + session_ttl: Optional[int] = None, + state_ttl: Optional[int] = None, + ) -> None: + try: + from pymongo import MongoClient, ASCENDING + except ImportError: + raise ImportError( + "pymongo is required for MongoDB session storage. " + "Install with: pip install pymongo" + ) + + self._mongo_url = mongo_url or os.getenv("MONGODB_URL", "mongodb://localhost:27017") + self._db_name = db_name or os.getenv("MONGODB_DB", "dna") + self.session_ttl = session_ttl or int(os.getenv("SESSION_TTL_SECONDS", "28800")) + self.state_ttl = state_ttl or int(os.getenv("OAUTH_STATE_TTL", "600")) + + self._client = MongoClient( + self._mongo_url, + serverSelectionTimeoutMS=5000, + connectTimeoutMS=5000, + socketTimeoutMS=5000, + ) + db = self._client[self._db_name] + self._sessions = db["dna_sessions"] + self._states = db["dna_oauth_states"] + self._blocklist = db["dna_token_blocklist"] + + # Ensure TTL indexes exist (idempotent) + self._sessions.create_index( + [("expires_at", ASCENDING)], + expireAfterSeconds=0, + background=True, + ) + self._states.create_index( + [("expires_at", ASCENDING)], + expireAfterSeconds=0, + background=True, + ) + self._blocklist.create_index( + [("expires_at", ASCENDING)], + expireAfterSeconds=0, + background=True, + ) + + def _expires_at(self, ttl_seconds: int) -> datetime: + return datetime.fromtimestamp(time.time() + ttl_seconds, tz=timezone.utc) + + # ── Sessions ───────────────────────────────────────────────────────── # + + def create_session(self, session: UserSession) -> None: + doc = session.to_dict() + doc["_id"] = doc.pop("session_id") + doc["expires_at"] = self._expires_at(self.session_ttl) + self._sessions.insert_one(doc) + + def get_session(self, session_id: str) -> Optional[UserSession]: + doc = self._sessions.find_one({"_id": session_id}) + if doc is None: + return None + try: + doc["session_id"] = doc.pop("_id") + doc.pop("expires_at", None) + return UserSession.from_dict(doc) + except (KeyError, TypeError) as exc: + import warnings + warnings.warn( + f"[session_store] Failed to deserialize session '{session_id}': {exc}. " + "The session document may be from an older schema — deleting it.", + stacklevel=2, + ) + # Remove the corrupt document so the user is prompted to log in again + # rather than seeing repeated errors on every request. + try: + self._sessions.delete_one({"_id": session_id}) + except Exception: + pass + return None + + def update_session(self, session: UserSession) -> None: + doc = session.to_dict() + doc.pop("session_id") + doc["expires_at"] = self._expires_at(self.session_ttl) + self._sessions.replace_one( + {"_id": session.session_id}, + {**doc, "_id": session.session_id}, + upsert=True, + ) + + def delete_session(self, session_id: str) -> None: + self._sessions.delete_one({"_id": session_id}) + + def get_session_ttl(self, session_id: str) -> int: + doc = self._sessions.find_one({"_id": session_id}, {"expires_at": 1}) + if not doc or "expires_at" not in doc: + return -2 + remaining = doc["expires_at"].timestamp() - time.time() + return max(0, int(remaining)) + + # ── JWT blocklist ──────────────────────────────────────────────────── # + + def revoke_token(self, jti: str, remaining_ttl_seconds: int) -> None: + if remaining_ttl_seconds <= 0: + return + self._blocklist.replace_one( + {"_id": jti}, + {"_id": jti, "expires_at": self._expires_at(remaining_ttl_seconds)}, + upsert=True, + ) + + def is_token_revoked(self, jti: str) -> bool: + return self._blocklist.find_one({"_id": jti}) is not None + + # ── OAuth2 CSRF state ──────────────────────────────────────────────── # + + def store_oauth_state(self, state: str) -> None: + self._states.replace_one( + {"_id": state}, + {"_id": state, "expires_at": self._expires_at(self.state_ttl)}, + upsert=True, + ) + + def consume_oauth_state(self, state: str) -> bool: + """Atomically consume — findOneAndDelete is atomic in MongoDB.""" + result = self._states.find_one_and_delete({"_id": state}) + return result is not None + + # ── Health ─────────────────────────────────────────────────────────── # + + def ping(self) -> bool: + try: + self._client.admin.command("ping") + return True + except Exception: + return False + + +# ── Singleton factory ───────────────────────────────────────────────────────── + +# Backward-compat alias — kept so any existing import of ``SessionStore`` still resolves. +SessionStore = AbstractSessionStore + +_session_store: Optional[AbstractSessionStore] = None + + +def get_session_store() -> AbstractSessionStore: + """Return the application-wide session store singleton (MongoDB).""" + global _session_store + if _session_store is None: + _session_store = MongoSessionStore() + return _session_store diff --git a/backend/src/dna/auth/shotgrid_auth_client.py b/backend/src/dna/auth/shotgrid_auth_client.py new file mode 100644 index 00000000..e290c329 --- /dev/null +++ b/backend/src/dna/auth/shotgrid_auth_client.py @@ -0,0 +1,443 @@ +"""ShotGrid Auth Client — uses ShotGrid's own OAuth2-like token endpoint. + +CONFIRMED FACTS (see INVESTIGATION_FINDINGS.md) +------------------------------------------------ +1. Autodesk Identity access_tokens CANNOT be used with ShotGrid (not GA, 2026). +2. Auth endpoint: POST /api/v1/auth/access_token (always v1, not v1.1) +3. Three supported grant types: + password → username + Legacy Password (requires user PAT on cloud) + client_credentials→ script_name + api_key + session_token → exchange existing SG session_token for Bearer token +4. Default access_token lifetime: 3600 seconds (1 hour), site-configurable. + The actual value is returned in the 'expires_in' field of every response. +5. Refresh tokens: supported via grant_type=refresh_token. +6. On-prem Docker sites: same endpoint, NO PAT required, uses actual SG password. + +RECOMMENDED AUTH FLOW FOR DNA (see INVESTIGATION_FINDINGS.md Q2) +----------------------------------------------------------------- +PRIMARY: AMI (Action Menu Item) flow — user launches DNA from within ShotGrid. + ShotGrid sends session_token in POST payload → exchange via session_token grant. + No PAT. No Legacy Password. Seamless SSO. + +FALLBACK: password grant — standalone access via Legacy Login + PAT. + Requires per-user PAT setup (cannot be admin-provisioned). + +BACKGROUND: client_credentials — script key for background jobs only. + +Environment variables: + SHOTGRID_URL - e.g. https://mystudio.shotgunstudio.com + SG_SITE_TYPE - "cloud" (default) or "onprem" + SG_ACCESS_TOKEN_TTL_BUFFER_SEC- refresh SG token N seconds before expiry (default 120) +""" + +from __future__ import annotations + +import os +import time +from dataclasses import dataclass, field +from typing import Optional + +import requests + + +# ── Data classes ────────────────────────────────────────────────────────────── + + +@dataclass +class SGTokenSet: + """Tokens returned by ShotGrid's /api/v1/auth/access_token endpoint.""" + access_token: str + refresh_token: Optional[str] + token_type: str + expires_in: int # seconds — default 3600 (1 hour), site-configurable + obtained_at: float = field(default_factory=time.time) + + +@dataclass +class SGUserInfo: + """User info resolved from a ShotGrid access_token.""" + sg_user_id: int + email: str + name: str + login: str # ShotGrid login (needed for sudo_as_login if used) + + +# ── Client ──────────────────────────────────────────────────────────────────── + + +class ShotGridAuthClient: + """Handles all auth interactions with ShotGrid's own token endpoint. + + CONFIRMED: Autodesk Identity (APS) access_tokens CANNOT be used here. + ShotGrid operates its own legacy OAuth2-like token system entirely separate + from Autodesk Identity. + + Auth endpoint (both cloud and on-prem, always v1 not v1.1): + POST /api/v1/auth/access_token + Content-Type: application/x-www-form-urlencoded + """ + + # Class-level default — overridden per-instance in __init__ so that + # environment variable changes after import are picked up correctly. + _DEFAULT_TTL_BUFFER_SEC: int = 120 + + def __init__(self, sg_url: Optional[str] = None) -> None: + self.sg_url = (sg_url or os.getenv("SHOTGRID_URL") or "").rstrip("/") + if not self.sg_url: + raise ValueError("SHOTGRID_URL is required.") + # NOTE: Auth endpoint is always /api/v1/ — never /api/v1.1/ + self._token_url = f"{self.sg_url}/api/v1/auth/access_token" + self._is_onprem = os.getenv("SG_SITE_TYPE", "cloud").lower() == "onprem" + # Read at instantiation time so tests / runtime env changes take effect. + self.TTL_BUFFER_SEC: int = int( + os.getenv("SG_ACCESS_TOKEN_TTL_BUFFER_SEC", str(self._DEFAULT_TTL_BUFFER_SEC)) + ) + + # ── Grant: session_token (AMI flow — primary, no PAT needed) ─────── # + + def login_via_session_token(self, session_token: str) -> SGTokenSet: + """Exchange an existing ShotGrid session_token for a Bearer token. + + This is the RECOMMENDED primary auth path for DNA. + + ShotGrid sends a session_token to DNA when the user launches it from + the ShotGrid UI via an Action Menu Item (AMI). The backend exchanges + it here for a proper Bearer access_token. + + No PAT. No Legacy Password. No Autodesk Identity interaction. + The resulting access_token IS the user's session — ShotGrid enforces + their native project permissions on all subsequent API calls. + + AMI Setup (ShotGrid Admin must do once): + ShotGrid Admin → Action Menu Items → Create AMI + Entity Types: Playlist (or Version, etc.) + URL: POST https://your-dna-backend.com/auth/ami-callback + Token type: User (sends the active user's session_token) + + Args: + session_token: The ShotGrid session_token received in the AMI POST + payload (key: 'session_token' in the request body). + + Returns: + SGTokenSet with access_token and refresh_token. + + Raises: + ValueError: ShotGrid rejected the session_token or is unreachable. + """ + return self._call_token_endpoint({ + "grant_type": "session_token", + "session_token": session_token, + }) + + # ── Grant: password (fallback for standalone access) ─────────────── # + + def login_user(self, username: str, password: str) -> SGTokenSet: + """Authenticate with ShotGrid username + Legacy Password. + + FALLBACK PATH: Use this only when DNA is accessed standalone (not + launched from ShotGrid via AMI). + + PAT requirement: + - Cloud ShotGrid: user MUST have a Personal Access Token (PAT) generated + at profile.autodesk.com and bound to their ShotGrid account. This + cannot be admin-provisioned — each user must do it once manually. + - On-prem/Enterprise Docker (SG_SITE_TYPE=onprem): PAT is NOT required. + Use the user's actual ShotGrid password (or LDAP/AD password if the + site is bound to the studio directory). + + Args: + username: ShotGrid username (usually the user's email on cloud sites). + password: For cloud: ShotGrid Legacy Login password (set in SG Account + Settings, separate from Autodesk account password). + For on-prem: actual ShotGrid or LDAP/AD password. + + Returns: + SGTokenSet with access_token and refresh_token. + + Raises: + ValueError: Authentication rejected or ShotGrid unreachable. + """ + return self._call_token_endpoint({ + "grant_type": "password", + "username": username, + "password": password, + }) + + # ── Grant: refresh_token ──────────────────────────────────────────── # + + def refresh_tokens(self, refresh_token: str) -> SGTokenSet: + """Obtain a new token set using a stored ShotGrid refresh_token. + + The SG access_token defaults to 3600s lifetime (1 hour, site-configurable). + The actual lifetime is in the expires_in field of the token response. + We call this proactively via should_refresh() 2 minutes before expiry. + + Args: + refresh_token: The ShotGrid refresh_token from the MongoDB session. + + Returns: + New SGTokenSet (new access_token + new refresh_token). + + Raises: + ValueError: Refresh token expired/revoked or ShotGrid unreachable. + On 401: caller should require the user to re-login via + the AMI flow or password grant. + """ + return self._call_token_endpoint({ + "grant_type": "refresh_token", + "refresh_token": refresh_token, + }) + + # ── User info ─────────────────────────────────────────────────────── # + + def get_user_info(self, access_token: str, username: str = None) -> SGUserInfo: + """Resolve a ShotGrid HumanUser from an authenticated access_token. + + Two code paths: + + 1. ``username`` provided (PAT / password grant): + Look up the user by email using script credentials or fall back to + trusting the authenticated email directly. + + 2. ``username`` is None (AMI / SSO / session_token grant): + Decode the ShotGrid Bearer JWT (no signature verification — SG + issued it, we trust it) to extract the ``identity.id`` claim, then + fetch the full user record via script credentials. + + ShotGrid REST API JWTs carry: + { "identity": { "type": "HumanUser", "id": }, ... } + or { "sub": "HumanUser:", ... } + + Fallback (no script creds): make a REST call with the Bearer token + to ``/api/v1/entity/HumanUsers/``. + """ + sg_script = os.getenv("SHOTGRID_SCRIPT_NAME") + sg_key = os.getenv("SHOTGRID_API_KEY") + + # ── Path 1: username known (password grant) ──────────────────── # + if username: + if sg_script and sg_key: + try: + from shotgun_api3 import Shotgun + sg = Shotgun(self.sg_url, script_name=sg_script, api_key=sg_key) + user = sg.find_one( + "HumanUser", + filters=[["email", "is", username]], + fields=["id", "name", "email", "login"], + ) + if user: + return SGUserInfo( + sg_user_id=int(user["id"]), + email=(user.get("email") or username).lower().strip(), + name=user.get("name") or username, + login=user.get("login") or username, + ) + except Exception as exc: + raise ValueError( + f"Could not look up user '{username}' in ShotGrid: {exc}" + ) + # No script credentials — cannot look up user without them. + # Returning sg_user_id=0 would silently break permission enforcement + # on all subsequent ShotGrid API calls. Fail loudly instead. + raise ValueError( + f"Cannot look up ShotGrid user '{username}': " + "SHOTGRID_SCRIPT_NAME and SHOTGRID_API_KEY are required to resolve " + "user identity. Set them in your environment." + ) + + # ── Path 2: username unknown (AMI / SSO / session_token grant) ── # + # Decode the ShotGrid Bearer JWT to extract the HumanUser ID. + user_id = self._extract_user_id_from_jwt(access_token) + + if user_id and sg_script and sg_key: + try: + from shotgun_api3 import Shotgun + sg = Shotgun(self.sg_url, script_name=sg_script, api_key=sg_key) + user = sg.find_one( + "HumanUser", + filters=[["id", "is", user_id]], + fields=["id", "name", "email", "login"], + ) + if user: + return SGUserInfo( + sg_user_id=int(user["id"]), + email=(user.get("email") or "").lower().strip(), + name=user.get("name") or "", + login=user.get("login") or "", + ) + except Exception as exc: + raise ValueError( + f"Could not fetch HumanUser(id={user_id}) from ShotGrid: {exc}" + ) + + # Fallback: REST call with the Bearer token itself. + if user_id: + try: + resp = requests.get( + f"{self.sg_url}/api/v1/entity/HumanUsers/{user_id}", + headers={"Authorization": f"Bearer {access_token}"}, + params={"fields": "id,name,email,login"}, + timeout=10, + ) + if resp.ok: + attrs = resp.json().get("data", {}).get("attributes", {}) + return SGUserInfo( + sg_user_id=user_id, + email=(attrs.get("email") or "").lower().strip(), + name=attrs.get("name") or "", + login=attrs.get("login") or "", + ) + except Exception: + pass + + raise ValueError( + "Could not resolve user identity from ShotGrid access_token. " + "Ensure SHOTGRID_SCRIPT_NAME and SHOTGRID_API_KEY are configured, " + "or check that the ShotGrid JWT contains an 'identity' claim." + ) + + @staticmethod + def _extract_user_id_from_jwt(access_token: str) -> Optional[int]: + """Decode a ShotGrid Bearer JWT (no verification) and return HumanUser ID. + + ShotGrid REST API access_tokens are JWTs. The user identity is in: + • ``identity.id`` (preferred, newer SG format) + • ``sub`` (may be int or "HumanUser:42") + + Returns None if decoding fails or no user ID is found. + """ + import base64 + import json as _json + + try: + parts = access_token.split(".") + if len(parts) != 3: + return None + # Pad to a valid base64 length + payload_b64 = parts[1] + "=" * (4 - len(parts[1]) % 4) + payload = _json.loads(base64.urlsafe_b64decode(payload_b64)) + + # Preferred: identity.type == HumanUser and identity.id + identity = payload.get("identity") or {} + if isinstance(identity, dict) and identity.get("type") == "HumanUser": + uid = identity.get("id") + if uid: + return int(uid) + + # Fallback: sub claim — either int or "HumanUser:42" + sub = payload.get("sub") + if sub is not None: + if isinstance(sub, int): + return sub + s = str(sub) + if ":" in s: + return int(s.split(":")[-1]) + return int(s) + except Exception: + pass + return None + + # ── Token lifecycle helpers ───────────────────────────────────────── # + + def should_refresh(self, token_set: SGTokenSet) -> bool: + """Return True if the SG access_token should be refreshed now. + + The SG token lifetime is returned in expires_in (default 3600s). + We refresh TTL_BUFFER_SEC (120s) before expiry. + """ + elapsed = time.time() - token_set.obtained_at + return elapsed >= (token_set.expires_in - self.TTL_BUFFER_SEC) + + @staticmethod + def is_expired(token_set: SGTokenSet) -> bool: + """Return True if the SG access_token has definitely expired.""" + elapsed = time.time() - token_set.obtained_at + return elapsed >= token_set.expires_in + + # ── Internal ──────────────────────────────────────────────────────── # + + def _call_token_endpoint(self, payload: dict) -> SGTokenSet: + """POST to ShotGrid's /api/v1/auth/access_token endpoint. + + Raises: + ValueError: HTTP error or malformed response. + """ + try: + resp = requests.post( + self._token_url, + data=payload, + headers={"Content-Type": "application/x-www-form-urlencoded"}, + timeout=15, + ) + except requests.ConnectionError: + raise ValueError( + f"Cannot reach ShotGrid auth endpoint at {self._token_url}. " + "Check SHOTGRID_URL and network connectivity." + ) + except requests.Timeout: + raise ValueError("ShotGrid auth endpoint timed out after 15 seconds.") + + if resp.status_code == 401: + grant = payload.get("grant_type", "unknown") + if grant == "password": + if self._is_onprem: + hint = "Verify username and ShotGrid/LDAP password (on-prem site)." + else: + hint = ( + "Verify username and Legacy Login password. " + "Ensure a Personal Access Token (PAT) has been generated " + "at profile.autodesk.com and bound to the ShotGrid account." + ) + elif grant == "session_token": + hint = "The ShotGrid session_token has expired or is invalid. User must re-launch from ShotGrid." + elif grant == "refresh_token": + hint = "The ShotGrid refresh_token has expired. User must re-authenticate." + else: + hint = "Verify credentials." + raise ValueError(f"ShotGrid authentication failed (HTTP 401). {hint}") + + if not resp.ok: + try: + body = resp.json() + errors = body.get("errors", []) + if errors and isinstance(errors, list): + first = errors[0] + detail = first.get("detail") or first.get("title") or str(first) + else: + detail = str(body)[:200] + except Exception: + detail = resp.text[:200] + raise ValueError( + f"ShotGrid auth endpoint failed (HTTP {resp.status_code}): {detail}" + ) + + body = resp.json() + access_token = body.get("access_token") + if not access_token: + raise ValueError( + f"ShotGrid token response missing 'access_token'. " + f"Keys received: {list(body.keys())}" + ) + + return SGTokenSet( + access_token=access_token, + refresh_token=body.get("refresh_token"), + token_type=body.get("token_type", "Bearer"), + # Use the actual value from the response — default 3600s (1 hour) + expires_in=int(body.get("expires_in", 3600)), + obtained_at=time.time(), + ) + + +# ── Singleton factory ───────────────────────────────────────────────────────── + + +_sg_auth_client: Optional[ShotGridAuthClient] = None + + +def get_sg_auth_client() -> ShotGridAuthClient: + """Return the application-wide ShotGridAuthClient singleton.""" + global _sg_auth_client + if _sg_auth_client is None: + _sg_auth_client = ShotGridAuthClient() + return _sg_auth_client diff --git a/backend/src/dna/auth_providers/auth_provider_base.py b/backend/src/dna/auth_providers/auth_provider_base.py index 83ac961e..8a3ad7ab 100644 --- a/backend/src/dna/auth_providers/auth_provider_base.py +++ b/backend/src/dna/auth_providers/auth_provider_base.py @@ -20,9 +20,9 @@ def validate_token(self, token: str) -> dict: Returns: A dictionary containing the token claims including: - - email: The user's email address - - sub: The user's unique ID - - name: The user's display name (if available) + - email: The user's email address + - sub: The user's unique ID + - name: The user's display name (if available) Raises: ValueError: If the token is invalid or expired. @@ -51,19 +51,31 @@ def get_user_email(self, token: str) -> str: def get_auth_provider() -> Optional[AuthProviderBase]: """Factory function to get the configured auth provider. + Reads the ``AUTH_PROVIDER`` environment variable (default: ``"none"``). + + Supported values: + ``none`` - No authentication (development/testing only). + ``shotgrid`` - ShotGrid PAT login (username + Legacy Password). + Requires ``SHOTGRID_URL`` and ``JWT_SECRET_KEY`` env vars. + Returns: - An AuthProviderBase instance based on AUTH_PROVIDER env var, - or None if AUTH_PROVIDER is 'none'. + An AuthProviderBase instance, or None when set to ``"none"``. + + Raises: + ValueError: If ``AUTH_PROVIDER`` is set to an unrecognised value. """ provider = os.getenv("AUTH_PROVIDER", "none").lower() if provider == "none": from dna.auth_providers.noop_auth_provider import NoopAuthProvider - return NoopAuthProvider() - elif provider == "google": - from dna.auth_providers.google_auth_provider import GoogleAuthProvider - return GoogleAuthProvider() + elif provider == "shotgrid": + from dna.auth_providers.shotgrid_sso import ShotGridSSOProvider + return ShotGridSSOProvider() + else: - raise ValueError(f"Unknown auth provider: {provider}") + raise ValueError( + f"Unknown auth provider: '{provider}'. " + f"Valid values: none, shotgrid." + ) diff --git a/backend/src/dna/auth_providers/google_auth_provider.py b/backend/src/dna/auth_providers/google_auth_provider.py deleted file mode 100644 index a9b8527a..00000000 --- a/backend/src/dna/auth_providers/google_auth_provider.py +++ /dev/null @@ -1,115 +0,0 @@ -"""Google Auth Provider. - -Validates Google tokens (ID tokens or access tokens) using Google APIs. -""" - -import os -import time -from typing import Optional - -import requests as http_requests -from google.auth.transport import requests -from google.oauth2 import id_token - -from dna.auth_providers.auth_provider_base import AuthProviderBase - - -class GoogleAuthProvider(AuthProviderBase): - """Google authentication provider that validates Google tokens.""" - - def __init__( - self, - client_id: Optional[str] = None, - ) -> None: - """Initialize the Google auth provider. - - Args: - client_id: The Google OAuth client ID for token validation. - If not provided, reads from GOOGLE_CLIENT_ID env var. - """ - self.client_id = client_id or os.getenv("GOOGLE_CLIENT_ID") - if not self.client_id: - raise ValueError("Google client ID is required for token validation") - self._request = requests.Request() - - def _validate_id_token(self, token: str) -> dict: - """Validate a Google ID token (JWT format).""" - claims = id_token.verify_oauth2_token( - token, - self._request, - self.client_id, - ) - if not claims.get("email_verified", False): - raise ValueError("Email not verified") - return claims - - def _validate_access_token(self, token: str) -> dict: - """Validate a Google access token using tokeninfo endpoint. - - Note: Google's tokeninfo API only supports GET with the token in the - query string; avoid logging or proxying request URLs to prevent token leakage. - """ - response = http_requests.get( - f"https://oauth2.googleapis.com/tokeninfo?access_token={token}" - ) - if response.status_code != 200: - raise ValueError(f"Invalid access token: {response.text}") - - token_info = response.json() - - if "error" in token_info: - raise ValueError(f"Token error: {token_info['error']}") - - if "exp" in token_info: - exp = token_info["exp"] - exp_ts = int(exp) if isinstance(exp, str) else exp - if exp_ts < time.time(): - raise ValueError("Access token expired") - - if self.client_id and token_info.get("aud") != self.client_id: - raise ValueError("Invalid audience") - - userinfo_response = http_requests.get( - "https://www.googleapis.com/oauth2/v3/userinfo", - headers={"Authorization": f"Bearer {token}"}, - ) - if userinfo_response.status_code != 200: - raise ValueError("Failed to get user info") - - userinfo = userinfo_response.json() - - return { - "sub": userinfo.get("sub"), - "email": userinfo.get("email"), - "email_verified": userinfo.get("email_verified", False), - "name": userinfo.get("name"), - "picture": userinfo.get("picture"), - } - - def validate_token(self, token: str) -> dict: - """Validate a Google token (ID token or access token). - - Automatically detects token type based on format: - - JWT (3 dot-separated parts) -> ID token validation - - Other -> Access token validation - - Args: - token: The Google token to validate. - - Returns: - A dictionary containing user info including: - - email: The user's email address - - sub: The user's unique Google ID - - name: The user's display name (if available) - - picture: URL to the user's profile picture (if available) - - Raises: - ValueError: If the token is invalid or expired. - """ - try: - if token.count(".") == 2: - return self._validate_id_token(token) - else: - return self._validate_access_token(token) - except Exception as e: - raise ValueError(f"Invalid token: {e}") diff --git a/backend/src/dna/auth_providers/shotgrid_sso.py b/backend/src/dna/auth_providers/shotgrid_sso.py new file mode 100644 index 00000000..7dd1c4ef --- /dev/null +++ b/backend/src/dna/auth_providers/shotgrid_sso.py @@ -0,0 +1,317 @@ +"""ShotGrid PAT Auth Provider. + +Authenticates users via ShotGrid username + Legacy Password (Personal Access +Token path). ShotGrid tokens are stored server-side in MongoDB — they are +never sent to the browser. The browser receives only a short-lived DNA JWT. + +Auth flow +--------- +1. Browser POSTs username + password to POST /auth/login +2. Backend calls ShotGrid's /api/v1/auth/access_token (password grant) + → ShotGrid validates credentials against its own user database +3. Backend calls ShotGrid HumanUser.find_one to resolve the real user identity + (email, name, integer sg_user_id — nothing hardcoded) +4. A UserSession is stored in MongoDB with the user's SG token +5. A minimal DNA JWT is returned to the browser (no credentials inside) +6. Every subsequent request: JWT verified → session fetched → SG query runs + under the user's own SG token → ShotGrid enforces native permissions + +Cloud ShotGrid requires PAT setup (once per user): + 1. profile.autodesk.com → Security → Personal Access Tokens → create + 2. ShotGrid → Account Settings → Legacy Login and PAT → bind PAT code +On-prem ShotGrid: use the actual ShotGrid / LDAP password, no PAT needed. +""" + +from __future__ import annotations + +import os +import time +import uuid +from typing import Optional + +try: + import jwt as pyjwt +except ImportError: + raise ImportError("PyJWT is required: pip install PyJWT") + +from dna.auth.session_store import SessionStore, ShotGridCredentials, UserSession +from dna.auth.shotgrid_auth_client import ShotGridAuthClient +from dna.auth_providers.auth_provider_base import AuthProviderBase + + +class ShotGridSSOProvider(AuthProviderBase): + """Production auth provider — ShotGrid username + Legacy Password (PAT).""" + + _REFRESH_GRACE_SECONDS = 60 + + def __init__( + self, + session_store: Optional[SessionStore] = None, + sg_auth_client: Optional[ShotGridAuthClient] = None, + ) -> None: + self._secret = os.getenv("JWT_SECRET_KEY", "CHANGE_ME_USE_A_REAL_SECRET_32CHARS") + self._algorithm = os.getenv("JWT_ALGORITHM", "HS256") + self._expire_seconds = int(os.getenv("JWT_EXPIRE_MINUTES", "480")) * 60 + + _INSECURE_DEFAULT = "CHANGE_ME_USE_A_REAL_SECRET_32CHARS" + if self._secret == _INSECURE_DEFAULT: + raise ValueError( + "JWT_SECRET_KEY is set to the insecure placeholder value. " + "Generate a secure secret with: openssl rand -hex 32 " + "and set it in your environment before starting the server." + ) + + self._sessions: SessionStore = session_store or _lazy_session_store() + # _sg_auth is initialised lazily via _get_sg_auth() to avoid failing + # at startup when SHOTGRID_URL is not yet configured. + self._sg_auth_override: Optional[ShotGridAuthClient] = sg_auth_client + + def _get_sg_auth(self) -> "ShotGridAuthClient": + """Return the ShotGrid auth client, initialising it on first use.""" + if self._sg_auth_override is not None: + return self._sg_auth_override + return _lazy_sg_auth_client() + + # ── AuthProviderBase ──────────────────────────────────────────────── # + + def validate_token(self, token: str) -> dict: + """Validate DNA JWT and check revocation blocklist. + + Raises: + ValueError: Missing, malformed, expired, or revoked token. + """ + claims = self._decode_jwt(token) + jti = claims.get("jti") + if not jti: + raise ValueError("Token is missing the 'jti' claim.") + if self._sessions.is_token_revoked(jti): + raise ValueError("Token has been revoked. Please log in again.") + return claims + + # ── PAT login ─────────────────────────────────────────────────────── # + + def login(self, username: str, password: str) -> dict: + """Authenticate with ShotGrid username + Legacy Password. + + Args: + username: ShotGrid username (email on cloud, login on some on-prem sites). + password: ShotGrid Legacy Login password (cloud) or actual password (on-prem). + + Returns: + Auth token response dict with 'access_token' (DNA JWT). + + Raises: + ValueError: SG rejected credentials, or user info missing. + """ + sg_token_set = self._get_sg_auth().login_user(username, password) + user_info = self._get_sg_auth().get_user_info( + sg_token_set.access_token, username=username + ) + + session_id = str(uuid.uuid4()) + jti = str(uuid.uuid4()) + session = UserSession( + session_id=session_id, + jti=jti, + email=user_info.email, + name=user_info.name, + auth_provider="shotgrid_pat", + shotgrid=ShotGridCredentials( + user_id=user_info.sg_user_id, + username=username, # ShotGrid login name — never overwritten + access_token=sg_token_set.access_token, # Bearer token — rotated on refresh + refresh_token=sg_token_set.refresh_token, + password=password, # stored server-side, never sent to client + ), + ) + self._sessions.create_session(session) + + access_token = self._mint_jwt( + jti, session_id, user_info.email, user_info.name, user_info.sg_user_id + ) + return { + "access_token": access_token, + "token_type": "Bearer", + "expires_in": self._expire_seconds, + "refresh_token": None, + "user": { + "id": user_info.sg_user_id, + "email": user_info.email, + "name": user_info.name, + "shotgrid_user_id": user_info.sg_user_id, + }, + } + + # ── Login info (mode detection for frontend) ─────────────────────── # + + def get_login_info(self) -> dict: + """Return available auth modes so the frontend renders the correct login UI. + + Returns PAT-only mode. + """ + return { + "modes": { + "shotgrid_pat": {"enabled": True}, + "shotgrid_sso": {"enabled": False}, + "google": {"enabled": False}, + }, + "mode": "pat", + } + + # ── Token refresh ─────────────────────────────────────────────────── # + + def refresh_access_token(self, expired_jwt: str) -> dict: + """Rotate DNA JWT + underlying SG tokens. + + SG access_token lifetime = 3600s (1 hour, site-configurable). + DNA JWT lifetime = 480 min (8 hours, configurable via JWT_EXPIRE_MINUTES). + """ + claims = self._decode_jwt(expired_jwt, allow_expired=True) + now = int(time.time()) + exp = claims.get("exp", 0) + + if now > (exp + self._REFRESH_GRACE_SECONDS): + raise ValueError("Token expired too long ago. Please log in again.") + + old_jti = claims.get("jti") + session_id = claims.get("session_id") + + if old_jti and self._sessions.is_token_revoked(old_jti): + raise ValueError("Token has been revoked. Please log in again.") + + session = self._sessions.get_session(session_id) + if session is None: + raise ValueError("Session not found or expired. Please log in again.") + + if not session.refresh_token: + raise ValueError( + "No ShotGrid refresh token in session. Please log in again." + ) + + try: + new_sg = self._get_sg_auth().refresh_tokens(session.refresh_token) + except ValueError as exc: + self._sessions.delete_session(session_id) + raise ValueError( + f"ShotGrid token refresh failed: {exc}. Please log in again." + ) + + new_jti = str(uuid.uuid4()) + session.sg_token = new_sg.access_token + session.refresh_token = new_sg.refresh_token + session.jti = new_jti + self._sessions.update_session(session) + + # Revoke old jti in blocklist + if old_jti: + remaining = max(0, exp - int(time.time())) + self._sessions.revoke_token(old_jti, remaining + self._REFRESH_GRACE_SECONDS) + + # Release stale pool slot (new SG token → new connection on next request) + _release_from_pool(session_id) + + access_token = self._mint_jwt( + new_jti, session_id, session.email, session.name, session.sg_user_id + ) + return { + "access_token": access_token, + "token_type": "Bearer", + "expires_in": self._expire_seconds, + "refresh_token": None, + "user": { + "id": session.sg_user_id, + "email": session.email, + "name": session.name, + "shotgrid_user_id": session.sg_user_id, + }, + } + + # ── Logout / revocation ───────────────────────────────────────────── # + + def revoke_token(self, token: str) -> None: + """Revoke JWT → add to blocklist + delete session + release pool.""" + try: + claims = self._decode_jwt(token, allow_expired=True) + except ValueError: + return + jti = claims.get("jti") + session_id = claims.get("session_id") + exp = claims.get("exp", 0) + if jti: + remaining = max(0, exp - int(time.time())) + self._sessions.revoke_token(jti, remaining + self._REFRESH_GRACE_SECONDS) + if session_id: + self._sessions.delete_session(session_id) + _release_from_pool(session_id) + + # ── Session retrieval (prodtrack dependency) ──────────────────────── # + + def get_session_for_request(self, token: str) -> UserSession: + """Validate JWT + blocklist check → return MongoDB session with sg_token. + + Called by get_user_scoped_prodtrack_provider() on every request. + The sg_token from the session is passed to ShotGridConnectionPool.get() + which returns a pooled Shotgun connection (no TCP handshake per request). + + ShotGrid enforces the user's native permissions on every .find() call + made through the connection — no extra filtering needed in app code. + """ + claims = self.validate_token(token) + session_id = claims.get("session_id") + if not session_id: + raise ValueError("Token is missing 'session_id' claim.") + session = self._sessions.get_session(session_id) + if session is None: + raise ValueError("Session has expired. Please log in again.") + return session + + # ── Internal ──────────────────────────────────────────────────────── # + + def _mint_jwt(self, jti, session_id, email, name, sg_user_id) -> str: + """Mint a signed DNA JWT. No SG token inside — server-side only.""" + now = int(time.time()) + payload = { + "jti": jti, + "sub": str(sg_user_id), + "session_id": session_id, + "email": email, + "name": name or email, + "iat": now, + "exp": now + self._expire_seconds, + } + return pyjwt.encode(payload, self._secret, algorithm=self._algorithm) + + def _decode_jwt(self, token: str, allow_expired: bool = False) -> dict: + options = {"verify_exp": not allow_expired} + try: + return pyjwt.decode( + token, self._secret, algorithms=[self._algorithm], options=options + ) + except pyjwt.ExpiredSignatureError: + raise ValueError( + "Token has expired. Use POST /auth/refresh or log in again." + ) + except pyjwt.InvalidTokenError as exc: + raise ValueError(f"Invalid authentication token: {exc}") + + +# ── Lazy singletons ─────────────────────────────────────────────────────────── + +def _lazy_session_store(): + from dna.auth.session_store import get_session_store + return get_session_store() + +def _lazy_sg_auth_client(): + from dna.auth.shotgrid_auth_client import get_sg_auth_client + return get_sg_auth_client() + +def _release_from_pool(session_id: str) -> None: + try: + from dna.auth.connection_pool import get_connection_pool + get_connection_pool().release(session_id) + except Exception as exc: + import warnings + warnings.warn( + f"[shotgrid_sso] Failed to release pool entry for session '{session_id}': {exc}", + stacklevel=2, + ) diff --git a/backend/src/dna/prodtrack_providers/prodtrack_provider_base.py b/backend/src/dna/prodtrack_providers/prodtrack_provider_base.py index 5a566028..3df55704 100644 --- a/backend/src/dna/prodtrack_providers/prodtrack_provider_base.py +++ b/backend/src/dna/prodtrack_providers/prodtrack_provider_base.py @@ -3,6 +3,9 @@ import os from datetime import date from typing import TYPE_CHECKING, Any +import os +from abc import ABC, abstractmethod +from typing import TYPE_CHECKING, Any, Optional if TYPE_CHECKING: from dna.models.entity import EntityBase, Playlist, Project, User, Version @@ -10,11 +13,17 @@ class UserNotFoundError(Exception): """Raised when a user is not found in the production tracking system.""" - pass -class ProdtrackProviderBase: +class ProdtrackProviderBase(ABC): + """Abstract base for all production tracking providers. + + Subclasses must implement every ``@abstractmethod``. Adding a new provider + (e.g. Ftrack) means subclassing this and implementing all methods — no + changes to callers or this base class are needed (Open/Closed Principle). + """ + def __init__(self): pass @@ -39,179 +48,56 @@ def build_version_context(version: Version) -> str: return "\n".join(parts) if parts else "No version context available." def _get_object_type(self, object_type: str) -> type["EntityBase"]: - """Get the model class from the entity type string.""" from dna.models.entity import ENTITY_MODELS, EntityBase - return ENTITY_MODELS.get(object_type, EntityBase) - def get_entity( - self, entity_type: str, entity_id: int, resolve_links: bool = True - ) -> "EntityBase": - """Get an entity by its ID. - - Args: - entity_type: The type of entity to fetch - entity_id: The ID of the entity - resolve_links: If True, recursively fetch linked entities. - If False, only include shallow links with id/name. - """ - raise NotImplementedError("Subclasses must implement this method.") + @abstractmethod + def get_entity(self, entity_type: str, entity_id: int, resolve_links: bool = True) -> "EntityBase": + """Fetch a single entity by type and ID.""" + @abstractmethod def add_entity(self, entity_type: str, entity: "EntityBase") -> "EntityBase": - """Add an entity to the production tracking system.""" - raise NotImplementedError("Subclasses must implement this method.") + """Create a new entity and return the persisted version.""" - def find( - self, entity_type: str, filters: list[dict[str, Any]], limit: int = 0 - ) -> list["EntityBase"]: - """Find entities matching the given filters. + @abstractmethod + def find(self, entity_type: str, filters: list[dict[str, Any]], limit: int = 0) -> list["EntityBase"]: + """Return entities matching the given filters.""" - Args: - entity_type: The DNA entity type to search for (e.g., 'shot', 'version') - filters: List of filter conditions in DNA format - limit: Maximum number of entities to return. Defaults to 0 (no limit). - - Returns: - List of matching entities - """ - raise NotImplementedError("Subclasses must implement this method.") - - def search( - self, - query: str, - entity_types: list[str], - project_id: int | None = None, - limit: int = 10, - ) -> list[dict[str, Any]]: - """Search for entities across multiple entity types. - - Args: - query: Text to search for (searches name field) - entity_types: List of entity types to search (e.g., ['user', 'shot', 'asset']) - project_id: Optional project ID to scope non-user entities - limit: Maximum results per entity type - - Returns: - List of lightweight entity representations with type, id, name, and - type-specific fields (email for users, description for shots/assets/versions) - """ - raise NotImplementedError("Subclasses must implement this method.") + @abstractmethod + def search(self, query: str, entity_types: list[str], project_id: int | None = None, limit: int = 10) -> list[dict[str, Any]]: + """Full-text search across one or more entity types.""" + @abstractmethod def get_user_by_email(self, user_email: str) -> "User": - """Get a user by their email address. - - Args: - user_email: The email address of the user - - Returns: - User entity with name, email, and login - - Raises: - ValueError: If user is not found - """ - raise NotImplementedError("Subclasses must implement this method.") + """Return the User record for the given email address.""" + @abstractmethod def get_projects_for_user(self, user_email: str) -> list["Project"]: - """Get projects accessible by a user. - - Args: - user_email: The email address of the user - - Returns: - List of Project entities the user has access to - """ - raise NotImplementedError("Subclasses must implement this method.") + """Return projects accessible by the given user.""" + @abstractmethod def get_playlists_for_project(self, project_id: int) -> list["Playlist"]: - """Get playlists for a project. - - Args: - project_id: The ID of the project - - Returns: - List of Playlist entities for the project - """ - raise NotImplementedError("Subclasses must implement this method.") + """Return all playlists belonging to the project.""" + @abstractmethod def get_versions_for_playlist(self, playlist_id: int) -> list["Version"]: - """Get versions for a playlist. - - Args: - playlist_id: The ID of the playlist - - Returns: - List of Version entities in the playlist - """ - raise NotImplementedError("Subclasses must implement this method.") - - def get_version_statuses( - self, project_id: int | None = None - ) -> list[dict[str, str]]: - """Get valid status values for Versions. - - Args: - project_id: Optional project ID to scope status values + """Return all versions in the playlist.""" - Returns: - List of status dicts with 'code' and 'name' keys - """ - raise NotImplementedError("Subclasses must implement this method.") + @abstractmethod + def get_version_statuses(self, project_id: int | None = None) -> list[dict[str, str]]: + """Return valid version status codes (optionally scoped to a project).""" - def publish_note( - self, - version_id: int, - content: str, - subject: str, - to_users: list[int], - cc_users: list[int], - links: list["EntityBase"], - author_email: str | None = None, - version_status: str | None = None, - ) -> int: - """Publish a note to the production tracking system. - - Args: - version_id: The ID of the version (or other entity) to link to - content: Note content - subject: Note subject - to_users: List of user IDs to address - cc_users: List of user IDs to CC - links: List of additional entities to link - author_email: Optional email of the author. If provided, the note - should be created on behalf of this user. - version_status: Optional status code to set on the version. - - Returns: - The ID of the created note - """ - raise NotImplementedError("Subclasses must implement this method.") + @abstractmethod + def publish_note(self, version_id: int, content: str, subject: str, to_users: list[int], cc_users: list[int], links: list["EntityBase"], author_email: str | None = None, version_status: str | None = None) -> int: + """Create and publish a note; return the new note ID.""" + @abstractmethod def update_version_status(self, version_id: int, status: str) -> bool: - """Update the status of a version without publishing a note. + """Update the status of a version. Returns True on success.""" - Args: - version_id: The ID of the version to update - status: The status code to set - - Returns: - True if the update succeeded, False otherwise - """ - raise NotImplementedError("Subclasses must implement this method.") - - def attach_file_to_note( - self, note_id: int, file_path: str, display_name: str - ) -> bool: - """Upload a local file as an attachment on an existing note. - - Args: - note_id: The ID of the note to attach the file to - file_path: Absolute path to the local file - display_name: Filename to display in the tracking system - - Returns: - True if upload succeeded, False otherwise - """ - raise NotImplementedError("Subclasses must implement this method.") + @abstractmethod + def attach_file_to_note(self, note_id: int, file_path: str, display_name: str) -> bool: + """Attach a local file to an existing note. Returns True on success.""" def publish_transcript( self, @@ -251,27 +137,64 @@ def update_transcript( raise NotImplementedError("Subclasses must implement this method.") -def get_prodtrack_provider() -> ProdtrackProviderBase: - """Get the production tracking provider.""" +def get_prodtrack_provider( + user_token: Optional[str] = None, + session_id: Optional[str] = None, +) -> ProdtrackProviderBase: + """Get the production tracking provider. + + Args: + user_token: ShotGrid session token from the user's MongoDB session. + When provided, queries run as this user and ShotGrid + enforces their native permissions. + session_id: The user's DNA session ID. Used to retrieve a pooled + SG connection from ShotGridConnectionPool, avoiding a + new TCP handshake per request. + + Returns: + Configured ProdtrackProviderBase instance. + + Raises: + ValueError: Unknown provider or missing credentials. + """ provider_type = os.getenv("PRODTRACK_PROVIDER", "shotgrid") if provider_type == "mock": from dna.prodtrack_providers.mock_provider import MockProdtrackProvider - return MockProdtrackProvider() if provider_type == "shotgrid": sg_url = os.getenv("SHOTGRID_URL") - sg_script = os.getenv("SHOTGRID_SCRIPT_NAME") - sg_key = os.getenv("SHOTGRID_API_KEY") - if not all([sg_url, sg_script, sg_key]): + if not sg_url: raise ValueError( - "ShotGrid credentials not provided. Set SHOTGRID_URL, " - "SHOTGRID_SCRIPT_NAME, and SHOTGRID_API_KEY, or use " - "PRODTRACK_PROVIDER=mock for the mock provider." + "SHOTGRID_URL is required. Use PRODTRACK_PROVIDER=mock for local dev." ) from dna.prodtrack_providers.shotgrid import ShotgridProvider - return ShotgridProvider() - - raise ValueError(f"Unknown production tracking provider: {provider_type}") + if user_token: + # user_token is the ShotGrid Bearer token — used as a presence signal. + # For login+password auth (PAT path), we retrieve username and password + # from the session to build a shotgun_api3 connection. + from dna.auth.session_store import get_session_store + store = get_session_store() + session = store.get_session(session_id) if session_id else None + if session and session.sg_password and session.sg_username: + return ShotgridProvider( + login=session.sg_username, # ShotGrid login name (never Bearer token) + password=session.sg_password, # legacy password stored server-side + session_id=session_id, + ) + # Fallback: no stored password (e.g. future SSO path) — use sudo via script creds + return ShotgridProvider(sudo_user=user_token, session_id=session_id) + else: + # Script-auth fallback: background jobs / non-SG-SSO auth providers. + sg_script = os.getenv("SHOTGRID_SCRIPT_NAME") + sg_key = os.getenv("SHOTGRID_API_KEY") + if not all([sg_script, sg_key]): + raise ValueError( + "Script credentials missing. Set SHOTGRID_SCRIPT_NAME and " + "SHOTGRID_API_KEY, or use PRODTRACK_PROVIDER=mock." + ) + return ShotgridProvider() + + raise ValueError(f"Unknown PRODTRACK_PROVIDER: '{provider_type}'. Valid: mock, shotgrid.") diff --git a/backend/src/dna/prodtrack_providers/shotgrid.py b/backend/src/dna/prodtrack_providers/shotgrid.py index c07460e2..2afe53e8 100644 --- a/backend/src/dna/prodtrack_providers/shotgrid.py +++ b/backend/src/dna/prodtrack_providers/shotgrid.py @@ -1,4 +1,24 @@ -"""ShotGrid production tracking provider implementation.""" +"""ShotGrid production tracking provider implementation. + +Authentication modes +-------------------- +**User-token mode** (``user_token`` provided — production): + Opens ``Shotgun(url, session_token=user_token)``. ShotGrid natively + enforces that user's project permissions on every API call. The + connection is retrieved from ``ShotGridConnectionPool`` — no new TCP + handshake per request. + +**Script-auth mode** (``user_token`` is None — dev / background jobs): + Uses ``SHOTGRID_SCRIPT_NAME`` + ``SHOTGRID_API_KEY`` credentials. + Never use for user-facing requests in production. + +Connection pool +--------------- +When ``user_token`` and ``session_id`` are both provided, the provider +uses ``ShotGridConnectionPool.get()`` instead of constructing a new +``Shotgun()`` instance. This is the fast path for all authenticated +user requests. +""" import contextlib import os @@ -20,20 +40,10 @@ UserNotFoundError, ) -# Field Mappings map the DNA entity to the SG entity. -# Key: DNA entity Name -# Entity_id: The SG entity Type. -# Fields: A mapping between the SG field ID and the DNA field ID. These -# Are used as fields in the find query. Source->Destination. -# Linked_fields: Like the fields mapping key except these ref to entities. We -# TODO: This may not be needed if we use a schema field read. FIELD_MAPPING = { "project": { "entity_id": "Project", - "fields": { - "id": "id", - "name": "name", - }, + "fields": {"id": "id", "name": "name"}, "linked_fields": {}, }, "shot": { @@ -129,42 +139,82 @@ def __init__( api_key: Optional[str] = None, sudo_user: Optional[str] = None, connect: bool = True, + user_token: Optional[str] = None, + session_id: Optional[str] = None, + login=None, + password=None, ): """Initialize the ShotGrid connection. Args: - url: ShotGrid server URL. Defaults to SHOTGRID_URL env var. - script_name: API script name. Defaults to SHOTGRID_SCRIPT_NAME env var. - api_key: API key for authentication. Defaults to SHOTGRID_API_KEY env var. - sudo_user: Optional user login to perform actions as. - connect: Whether to connect immediately. + url: ShotGrid server URL. Defaults to SHOTGRID_URL. + script_name: API script name. Defaults to SHOTGRID_SCRIPT_NAME. + api_key: API key. Defaults to SHOTGRID_API_KEY. + sudo_user: Sudo user login (script-auth only). + connect: Whether to connect immediately (script-auth only). + user_token: ShotGrid session token for the authenticated user. + Takes priority over script credentials. + session_id: The user's session ID from the DNA JWT. + When provided alongside user_token, connections are + retrieved from the pool instead of created fresh. """ super().__init__() - self.url = url or os.getenv("SHOTGRID_URL") + self.url: str = (url or os.getenv("SHOTGRID_URL") or "").rstrip("/") + self._sudo_connection: Optional[Shotgun] = None + + if not self.url: + raise ValueError("SHOTGRID_URL is required.") + + # ── User-token mode (production) ──────────────────────────────── # + if user_token: + # user_token is not used for connection — sudo_user is set instead + # This branch is no longer reached after prodtrack_provider_base change + pass + + if login and password: + self.user_token = None + self.session_id = session_id + self.script_name = None + self.api_key = None + self.sudo_user = None + self.sg = Shotgun(self.url, login=login, password=password) + return + + # ── Script-auth mode (background jobs / dev) ──────────────────── # + self.user_token = None + self.session_id = None self.script_name = script_name or os.getenv("SHOTGRID_SCRIPT_NAME") self.api_key = api_key or os.getenv("SHOTGRID_API_KEY") self.sudo_user = sudo_user or os.getenv("SHOTGRID_SUDO_USER") - if not all([self.url, self.script_name, self.api_key]): + if not all([self.script_name, self.api_key]): raise ValueError( - "ShotGrid credentials not provided. Set SHOTGRID_URL, " - "SHOTGRID_SCRIPT_NAME, and SHOTGRID_API_KEY environment variables." + "ShotGrid script credentials not provided. Set SHOTGRID_SCRIPT_NAME " + "and SHOTGRID_API_KEY, or pass user_token for user-scoped auth." ) - self.sg = None - self._sudo_connection = None + self.sg: Optional[Shotgun] = None if connect: self.connect() - def connect(self, sudo_user: Optional[str] = None): - """Connect to ShotGrid. + def _get_connection( + self, user_token: str, session_id: Optional[str] + ) -> Shotgun: + """Get a SG connection from the pool (if session_id given) or create fresh.""" + if session_id: + try: + from dna.auth.connection_pool import get_connection_pool + return get_connection_pool().get( + session_id=session_id, sg_token=user_token + ) + except Exception: + pass # Pool unavailable — fall through to direct connection + # Direct connection fallback (no session_id, or pool error) + return Shotgun(self.url, session_token=user_token) - Args: - sudo_user: Optional user login to perform actions as. - If provided, overrides the instance's sudo_user. - """ - # Close existing connection if any (though Shotgun API doesn't really require explicit close) + def connect(self, sudo_user: Optional[str] = None) -> None: + """Connect using script credentials.""" self.sg = Shotgun( self.url, self.script_name, @@ -172,12 +222,8 @@ def connect(self, sudo_user: Optional[str] = None): sudo_as_login=sudo_user or self.sudo_user, ) - def set_sudo_user(self, sudo_user: str): - """Set the sudo user and re-initialize the connection. - - Args: - sudo_user: The user login to perform actions as. - """ + def set_sudo_user(self, sudo_user: str) -> None: + """Set sudo user and re-connect (script-auth only).""" self.sudo_user = sudo_user self.connect() @@ -185,14 +231,20 @@ def set_sudo_user(self, sudo_user: str): def sudo(self, user_login: str): """Context manager to perform actions as a specific user. - This creates a temporary connection for the duration of the context. + In user-token mode: the connection IS already the authenticated user, + so ShotGrid will record the correct author automatically. The sudo + context is a no-op in this mode. - Args: - user_login: The user login to perform actions as. + In script-auth mode: creates a temporary sudo connection. """ - original_connection = self._sudo_connection + if self.user_token: + # User-token mode: SG enforces identity natively — no sudo needed. + yield + return + + # Script-auth mode: create a temporary sudo connection. + original = self._sudo_connection try: - # Create a temporary connection for this user self._sudo_connection = Shotgun( self.url, self.script_name, @@ -201,13 +253,15 @@ def sudo(self, user_login: str): ) yield finally: - self._sudo_connection = original_connection + self._sudo_connection = original @property - def _sg(self): - """Get the active ShotGrid connection (sudo or main).""" + def _sg(self) -> Shotgun: + """Return the active ShotGrid connection (sudo override or main).""" return self._sudo_connection or self.sg + # ── Entity conversion ─────────────────────────────────────────────── # + def _convert_sg_entity_to_dna_entity( self, sg_entity: dict, @@ -218,34 +272,26 @@ def _convert_sg_entity_to_dna_entity( if entity_mapping is None: entity_mapping = FIELD_MAPPING.get(entity_type) if entity_mapping is None: - raise ValueError(f"No field mapping found for entity type: {entity_type}") + raise ValueError(f"No field mapping for entity type: {entity_type}") linked_fields_map = entity_mapping.get("linked_fields", {}) - - # Build DNA field values dict from SG response entity_data: dict = {} + for sg_name, dna_name in entity_mapping["fields"].items(): entity_data[dna_name] = sg_entity.get(sg_name) - # Populate linked fields for sg_field_name, dna_field_name in linked_fields_map.items(): linked_data = sg_entity.get(sg_field_name) - if resolve_links: - entity_data[dna_field_name] = self._resolve_linked_field(linked_data) - else: - entity_data[dna_field_name] = self._convert_shallow_link(linked_data) + entity_data[dna_field_name] = ( + self._resolve_linked_field(linked_data) + if resolve_links + else self._convert_shallow_link(linked_data) + ) - # Instantiate the Pydantic model model_class = ENTITY_MODELS[entity_type] - return model_class(**entity_data) def _convert_shallow_link(self, data): - """Convert linked entity data to shallow DNA entity without recursive fetch. - - This includes basic info (id, name) from the SG response without - making additional API calls to fetch the full entity. - """ if data is None: return None if isinstance(data, dict): @@ -255,14 +301,11 @@ def _convert_shallow_link(self, data): return None def _create_shallow_entity(self, sg_link: dict) -> EntityBase: - """Create a shallow DNA entity from a ShotGrid link dict.""" sg_type = sg_link.get("type") entity_id = sg_link.get("id") name = sg_link.get("name") - dna_type = _get_dna_entity_type(sg_type) model_class = ENTITY_MODELS[dna_type] - if dna_type == "playlist": return model_class(id=entity_id, code=name) return model_class(id=entity_id, name=name) @@ -317,7 +360,6 @@ def get_entity( return entity def _resolve_linked_field(self, data): - """Resolve linked entity data by fetching the full entity.""" if isinstance(data, dict): dna_type = _get_dna_entity_type(data["type"]) return self.get_entity(dna_type, data["id"], resolve_links=False) @@ -331,7 +373,6 @@ def _resolve_linked_field(self, data): return None def _convert_entities_to_sg_links(self, entities): - """Convert DNA entities to ShotGrid link format for creation.""" if isinstance(entities, EntityBase): return {"type": entities.__class__.__name__, "id": entities.id} elif isinstance(entities, list): @@ -342,14 +383,34 @@ def _convert_entities_to_sg_links(self, entities): ] return None - def add_entity(self, entity_type: str, entity: EntityBase) -> EntityBase: - """Add an entity to the production tracking system.""" + # ── CRUD ──────────────────────────────────────────────────────────── # + def get_entity( + self, entity_type: str, entity_id: int, resolve_links: bool = True + ) -> EntityBase: + if not self._sg: + raise ValueError("Not connected to ShotGrid") entity_mapping = FIELD_MAPPING.get(entity_type) if entity_mapping is None: raise ValueError(f"Unknown entity type: {entity_type}") + fields = list(entity_mapping["fields"].keys()) + linked_field_sg_names = list(entity_mapping.get("linked_fields", {}).keys()) + all_fields = list(set(fields + linked_field_sg_names)) + sg_entity = self._sg.find_one( + entity_mapping["entity_id"], + filters=[["id", "is", entity_id]], + fields=all_fields, + ) + if not sg_entity: + raise ValueError(f"Entity not found: {entity_type} {entity_id}") + return self._convert_sg_entity_to_dna_entity( + sg_entity, entity_mapping, entity_type, resolve_links=resolve_links + ) - # Map the entity fields to SG fields, skipping 'id' which is auto-generated + def add_entity(self, entity_type: str, entity: EntityBase) -> EntityBase: + entity_mapping = FIELD_MAPPING.get(entity_type) + if entity_mapping is None: + raise ValueError(f"Unknown entity type: {entity_type}") sg_entity_data = {} for sg_field_name, dna_field_name in entity_mapping["fields"].items(): if sg_field_name == "id": @@ -357,95 +418,53 @@ def add_entity(self, entity_type: str, entity: EntityBase) -> EntityBase: value = entity.model_dump().get(dna_field_name) if value is not None: sg_entity_data[sg_field_name] = value - - # Convert linked entities to SG format for creation linked_fields_to_preserve = {} - for sg_field_name, dna_field_name in entity_mapping.get( - "linked_fields", {} - ).items(): + for sg_field_name, dna_field_name in entity_mapping.get("linked_fields", {}).items(): linked_entities = getattr(entity, dna_field_name, None) if linked_entities is None: continue - linked_fields_to_preserve[dna_field_name] = linked_entities sg_linked = self._convert_entities_to_sg_links(linked_entities) if sg_linked: sg_entity_data[sg_field_name] = sg_linked - - # Create the entity in ShotGrid result = self._sg.create(entity_mapping["entity_id"], sg_entity_data) - - # Convert result and preserve linked entities from input created_entity = self._convert_sg_entity_to_dna_entity( result, entity_mapping, entity_type, resolve_links=False ) - - # Restore linked fields from the input entity since SG doesn't return them for dna_field_name, linked_entities in linked_fields_to_preserve.items(): setattr(created_entity, dna_field_name, linked_entities) - return created_entity def find( self, entity_type: str, filters: list[dict[str, Any]], limit: int = 0 ) -> list[EntityBase]: - """Find entities matching the given filters. - - Args: - entity_type: The DNA entity type to search for - filters: List of filter conditions in DNA format. - Each filter is a dict with 'field', 'operator', and 'value' keys. - limit: Maximum number of entities to return. Defaults to 0 (no limit). - - Returns: - List of matching DNA entities - """ if not self._sg: raise ValueError("Not connected to ShotGrid") - entity_mapping = FIELD_MAPPING.get(entity_type) if entity_mapping is None: raise ValueError(f"Unsupported entity type: {entity_type}") - - # Build reverse mapping from DNA field names to SG field names - dna_to_sg_fields = {v: k for k, v in entity_mapping["fields"].items()} - linked_fields_map = entity_mapping.get("linked_fields", {}) - dna_to_sg_fields.update({v: k for k, v in linked_fields_map.items()}) - - # Convert DNA filters to SG filters + dna_to_sg = {v: k for k, v in entity_mapping["fields"].items()} + dna_to_sg.update( + {v: k for k, v in entity_mapping.get("linked_fields", {}).items()} + ) sg_filters = [] for f in filters: - dna_field = f.get("field") - operator = f.get("operator") - value = f.get("value") - - sg_field = dna_to_sg_fields.get(dna_field) + sg_field = dna_to_sg.get(f.get("field")) if sg_field is None: - raise ValueError( - f"Unknown field '{dna_field}' for entity type '{entity_type}'" - ) - - sg_filters.append([sg_field, operator, value]) - - # Get all DNA fields to request from SG - sg_fields = list(entity_mapping["fields"].keys()) - linked_fields_map = entity_mapping.get("linked_fields", {}) - sg_fields.extend(linked_fields_map.keys()) - - # Query ShotGrid + raise ValueError(f"Unknown field '{f.get('field')}' for '{entity_type}'") + sg_filters.append([sg_field, f.get("operator"), f.get("value")]) + sg_fields = list(entity_mapping["fields"].keys()) + list( + entity_mapping.get("linked_fields", {}).keys() + ) sg_results = self._sg.find( entity_mapping["entity_id"], filters=sg_filters, fields=sg_fields, limit=limit, ) - - # Convert SG entities to DNA entities return [ - self._convert_sg_entity_to_dna_entity( - sg_entity, entity_mapping, entity_type - ) - for sg_entity in sg_results + self._convert_sg_entity_to_dna_entity(r, entity_mapping, entity_type) + for r in sg_results ] def search( @@ -455,44 +474,20 @@ def search( project_id: int | None = None, limit: int = 10, ) -> list[dict[str, Any]]: - """Search for entities across multiple entity types. - - Args: - query: Text to search for (searches name field) - entity_types: List of entity types to search (e.g., ['user', 'shot', 'asset']) - project_id: Optional project ID to scope non-user entities - limit: Maximum results per entity type - - Returns: - List of lightweight entity representations with type, id, name, and - type-specific fields (email for users, description for shots/assets/versions) - """ if not self.sg: raise ValueError("Not connected to ShotGrid") - results = [] - for entity_type in entity_types: - # Validate entity type entity_mapping = FIELD_MAPPING.get(entity_type) if entity_mapping is None: raise ValueError(f"Unsupported entity type: {entity_type}") - sg_entity_type = entity_mapping["entity_id"] - - # Determine the name field for this entity type (code or name) - # and build minimal fields list for performance fields_mapping = entity_mapping["fields"] - name_sg_field = None - for sg_field, dna_field in fields_mapping.items(): - if dna_field == "name": - name_sg_field = sg_field - break - + name_sg_field = next( + (sg for sg, dna in fields_mapping.items() if dna == "name"), None + ) if name_sg_field is None: continue - - # Build minimal fields list: only what we need for search results sg_fields = ["id", name_sg_field] if entity_type == "user": sg_fields.append("email") @@ -501,29 +496,17 @@ def search( sg_fields.append("description") if "project" in fields_mapping: sg_fields.append("project") - - # Build ShotGrid filters (empty query = prefetch up to limit, no name filter) q = (query or "").strip() - sg_filters: list[list[Any]] = [] + sg_filters: list = [] if q: sg_filters.append([name_sg_field, "contains", q]) - - # Add project filter for non-user entities if entity_type != "user" and project_id is not None: sg_filters.append( ["project", "is", {"type": "Project", "id": project_id}] ) - - # Query ShotGrid directly with minimal fields for performance sg_results = self.sg.find( - sg_entity_type, - filters=sg_filters, - fields=sg_fields, - limit=limit, + sg_entity_type, filters=sg_filters, fields=sg_fields, limit=limit ) - - # Convert to lightweight search results directly from SG response - # Use DNA model class name for proper type mapping model_class = ENTITY_MODELS.get(entity_type) dna_type = model_class.__name__ if model_class else entity_type.capitalize() for sg_entity in sg_results: @@ -532,144 +515,80 @@ def search( "id": sg_entity.get("id"), "name": sg_entity.get(name_sg_field), } - - # Add type-specific fields if entity_type == "user": result["email"] = sg_entity.get("email") else: - # Add description if present if "description" in sg_entity: result["description"] = sg_entity.get("description") - - # Add project reference if present project_data = sg_entity.get("project") if project_data: result["project"] = { "type": project_data.get("type"), "id": project_data.get("id"), } - results.append(result) - return results def get_user_by_email(self, user_email: str) -> User: - """Get a user by their email address. - - Args: - user_email: The email address of the user - - Returns: - User entity with name, email, and login - - Raises: - ValueError: If user is not found - """ if not self._sg: raise ValueError("Not connected to ShotGrid") - sg_user = self._sg.find_one( "HumanUser", filters=[["email", "is", user_email]], fields=["id", "name", "email", "login"], ) - if not sg_user: raise ValueError(f"User not found: {user_email}") - - entity_mapping = FIELD_MAPPING["user"] return self._convert_sg_entity_to_dna_entity( - sg_user, entity_mapping, "user", resolve_links=False + sg_user, FIELD_MAPPING["user"], "user", resolve_links=False ) def get_projects_for_user(self, user_email: str) -> list[Project]: - """Get projects accessible by a user. - - Args: - user_email: The email address of the user - - Returns: - List of Project entities the user has access to - """ if not self._sg: raise ValueError("Not connected to ShotGrid") - - # First, find the user by their email user = self._sg.find_one( "HumanUser", filters=[["email", "is", user_email]], fields=["id", "email", "name"], ) - if not user: raise ValueError(f"User not found: {user_email}") - - # Find projects where this user is in the users list sg_projects = self._sg.find( "Project", filters=[["users", "is", user]], fields=["id", "name"], ) - - entity_mapping = FIELD_MAPPING["project"] return [ self._convert_sg_entity_to_dna_entity( - sg_project, entity_mapping, "project", resolve_links=False + p, FIELD_MAPPING["project"], "project", resolve_links=False ) - for sg_project in sg_projects + for p in sg_projects ] def get_playlists_for_project(self, project_id: int) -> list[Playlist]: - """Get playlists for a project. - - Args: - project_id: The ID of the project - - Returns: - List of Playlist entities for the project - """ if not self._sg: raise ValueError("Not connected to ShotGrid") - sg_playlists = self._sg.find( "Playlist", - filters=[ - ["project", "is", {"type": "Project", "id": project_id}], - ], + filters=[["project", "is", {"type": "Project", "id": project_id}]], fields=["id", "code", "description", "project", "created_at", "updated_at"], ) - - entity_mapping = FIELD_MAPPING["playlist"] return [ self._convert_sg_entity_to_dna_entity( - sg_playlist, entity_mapping, "playlist", resolve_links=False + p, FIELD_MAPPING["playlist"], "playlist", resolve_links=False ) - for sg_playlist in sg_playlists + for p in sg_playlists ] def get_versions_for_playlist(self, playlist_id: int) -> list[Version]: - """Get versions for a playlist. - - Args: - playlist_id: The ID of the playlist - - Returns: - List of Version entities in the playlist - """ if not self._sg: raise ValueError("Not connected to ShotGrid") - sg_playlist = self._sg.find_one( - "Playlist", - filters=[["id", "is", playlist_id]], - fields=["versions"], + "Playlist", filters=[["id", "is", playlist_id]], fields=["versions"] ) - if not sg_playlist or not sg_playlist.get("versions"): return [] - version_ids = [v["id"] for v in sg_playlist["versions"]] - entity_mapping = FIELD_MAPPING["version"] version_fields = list(entity_mapping["fields"].keys()) + list( entity_mapping["linked_fields"].keys() @@ -679,8 +598,6 @@ def get_versions_for_playlist(self, playlist_id: int) -> list[Version]: filters=[["id", "in", version_ids]], fields=version_fields, ) - - # Collect unique task IDs from versions task_ids = list( { v["sg_task"]["id"] @@ -688,149 +605,81 @@ def get_versions_for_playlist(self, playlist_id: int) -> list[Version]: if v.get("sg_task") and v["sg_task"].get("id") } ) - - # Batch-fetch tasks with their step (pipeline_step) field tasks_by_id: dict[int, dict] = {} if task_ids: - task_mapping = FIELD_MAPPING["task"] - task_fields = list(task_mapping["fields"].keys()) - sg_tasks = self._sg.find( - "Task", - filters=[["id", "in", task_ids]], - fields=task_fields, - ) - for sg_task in sg_tasks: + task_fields = list(FIELD_MAPPING["task"]["fields"].keys()) + for sg_task in self._sg.find( + "Task", filters=[["id", "in", task_ids]], fields=task_fields + ): tasks_by_id[sg_task["id"]] = sg_task - # Fetch notes linked to this playlist or its versions - # We fetch notes linked to the playlist entity directly, OR linked to any of the versions. - # Note: SG API "in" filter for multi-entity links might be tricky for mixed types in one go if not careful. - # But we can query notes linked to the playlist, and notes linked to the versions. - # Let's try to get all relevant notes in one or two queries. - - # 1. Notes linked to Playlist - notes_by_version_id: dict[int, list[EntityBase]] = {} - - # Strategy: Fetch notes linked to the Playlist. Then check their version links. - # We assume the user email is available via deep linking in the 'created_by' field. sg_notes = self._sg.find( "Note", filters=[["note_links", "is", {"type": "Playlist", "id": playlist_id}]], fields=[ - "id", - "subject", - "content", - "note_links", - "created_by", - "created_by.HumanUser.email", # Fetch email directly - "created_at", + "id", "subject", "content", "note_links", + "created_by", "created_by.HumanUser.email", "created_at", ], ) - - # Process notes and assign to versions + notes_by_version_id: dict[int, list] = {} note_mapping = FIELD_MAPPING["note"] - notes_by_version_id: dict[int, list[EntityBase]] = {} - for sg_note in sg_notes: - # Convert to DNA Note dna_note = self._convert_sg_entity_to_dna_entity( sg_note, note_mapping, "note", resolve_links=False ) - - # Manually populate author email if present in the deep-linked field - if ( - sg_note.get("created_by") - and sg_note["created_by"].get("type") == "HumanUser" - ): + if sg_note.get("created_by") and sg_note["created_by"].get("type") == "HumanUser": email = sg_note.get("created_by.HumanUser.email") if email and dna_note.author: dna_note.author.email = email - - # Find linked versions - linked_vids = [] - # We need to look at the original SG note for links links = sg_note.get("note_links", []) - # Handle shallow links as dicts or list of dicts - if isinstance(links, list): - linked_vids = [l["id"] for l in links if l["type"] == "Version"] - elif isinstance(links, dict) and links["type"] == "Version": - linked_vids = [links["id"]] - + linked_vids = ( + [l["id"] for l in links if l["type"] == "Version"] + if isinstance(links, list) + else ([links["id"]] if isinstance(links, dict) and links["type"] == "Version" else []) + ) for vid in linked_vids: if vid in version_ids: - if vid not in notes_by_version_id: - notes_by_version_id[vid] = [] - notes_by_version_id[vid].append(dna_note) + notes_by_version_id.setdefault(vid, []).append(dna_note) - # Convert versions and enrich with full task data AND notes versions = [] for sg_version in sg_versions: version = self._convert_sg_entity_to_dna_entity( sg_version, entity_mapping, "version", resolve_links=False ) - # Replace shallow task with enriched task data if sg_version.get("sg_task") and sg_version["sg_task"].get("id"): task_id = sg_version["sg_task"]["id"] if task_id in tasks_by_id: - sg_task = tasks_by_id[task_id] - task_mapping = FIELD_MAPPING["task"] version.task = self._convert_sg_entity_to_dna_entity( - sg_task, task_mapping, "task", resolve_links=False + tasks_by_id[task_id], FIELD_MAPPING["task"], "task", resolve_links=False ) - - # Attach notes if version.id in notes_by_version_id: version.notes = notes_by_version_id[version.id] +<<<<<<< HEAD base = (self.url or "").rstrip("/") if base: version.prodtrack_detail_url = f"{base}/detail/Version/{version.id}" +======= +>>>>>>> 3328c7f (feat(auth): add ShotGrid PAT authentication for backend API endpoints) versions.append(version) - return versions - def get_version_statuses( - self, project_id: int | None = None - ) -> list[dict[str, str]]: - """Get valid status values for Versions. - - Args: - project_id: Optional project ID to scope status values - - Returns: - List of status dicts with 'code' and 'name' keys - """ + def get_version_statuses(self, project_id: int | None = None) -> list[dict[str, str]]: if not self.sg: raise ValueError("Not connected to ShotGrid") - - # Get schema for Version.sg_status_list field project_entity = {"type": "Project", "id": project_id} if project_id else None schema = self.sg.schema_field_read("Version", "sg_status_list", project_entity) - if not schema or "sg_status_list" not in schema: return [] - - field_info = schema["sg_status_list"] - properties = field_info.get("properties", {}) - valid_values = properties.get("valid_values", {}).get("value", []) - - # Build list of status dicts - display_values = properties.get("display_values", {}).get("value", {}) - statuses = [] - for code in valid_values: - statuses.append( - { - "code": code, - "name": display_values.get(code, code), - } - ) - - return statuses + props = schema["sg_status_list"].get("properties", {}) + valid_values = props.get("valid_values", {}).get("value", []) + display_values = props.get("display_values", {}).get("value", {}) + return [{"code": c, "name": display_values.get(c, c)} for c in valid_values] def update_version_status(self, version_id: int, status: str) -> bool: if not self._sg: - raise ValueError("Not connected to ShotGrid") + return False try: self._sg.update("Version", version_id, {"sg_status_list": status}) return True @@ -845,31 +694,15 @@ def update_note( version_id: Optional[int] = None, version_status: Optional[str] = None, ) -> bool: - """Update an existing note in ShotGrid. - - Args: - note_id: The ID of the note to update. - content: New content for the note. - subject: Optional new subject for the note. - version_id: Optional version ID to update status on. - version_status: Optional status code to set on the version. - - Returns: - True if successful, False otherwise. - """ if not self._sg: - raise ValueError("Not connected to ShotGrid") - + return False data = {"content": content} if subject: data["subject"] = subject - try: self._sg.update("Note", note_id, data) if version_status and version_id: - self._sg.update( - "Version", version_id, {"sg_status_list": version_status} - ) + self._sg.update("Version", version_id, {"sg_status_list": version_status}) return True except Exception as e: print(f"Error updating note {note_id}: {e}") @@ -886,118 +719,79 @@ def publish_note( author_email: Optional[str] = None, version_status: Optional[str] = None, ) -> int: - """Publish a note to ShotGrid. - - Args: - version_id: The ID of the version to link to. - content: Note content. - subject: Note subject. - to_users: List of user IDs to address. - cc_users: List of user IDs to CC. - links: List of additional entities to link. - author_email: Optional email of the author. - version_status: Optional status code to set on the version. - - Returns: - The ID of the created (or existing) note. - """ if not self._sg: raise ValueError("Not connected to ShotGrid") - - # 1. Fetch version to get Project and ensure version exists version_data = self._sg.find_one( - "Version", - filters=[["id", "is", version_id]], - fields=["project"], + "Version", filters=[["id", "is", version_id]], fields=["project"] ) if not version_data: raise ValueError(f"Version {version_id} not found") - project = version_data.get("project") if not project: raise ValueError(f"Version {version_id} has no project assigned") - # 2. Check for duplicates - # We consider a note a duplicate if it links to this version and has same subject/content - # Note: We don't check author because duplicate content from different author is still weird multiple post? - # Actually usually duplicate check includes author? Let's stick to subject+content+version link for now as per reference - duplicate_filters = [ - ["project", "is", project], - ["note_links", "is", {"type": "Version", "id": version_id}], - ["subject", "is", subject], - ["content", "is", content], - ] - - # Use find_one for efficiency, we just need to know if ANY exists - existing_note = self._sg.find_one( - "Note", filters=duplicate_filters, fields=["id"] + # Duplicate check + existing = self._sg.find_one( + "Note", + filters=[ + ["project", "is", project], + ["note_links", "is", {"type": "Version", "id": version_id}], + ["subject", "is", subject], + ["content", "is", content], + ], + fields=["id"], ) - if existing_note: + if existing: if version_status: - self._sg.update( - "Version", version_id, {"sg_status_list": version_status} - ) - return existing_note["id"] + self._sg.update("Version", version_id, {"sg_status_list": version_status}) + return existing["id"] - # 3. Prepare Note Data note_links = [{"type": "Version", "id": version_id}] if links: - extra_links = self._convert_entities_to_sg_links(links) - if extra_links: - if isinstance(extra_links, dict): - note_links.append(extra_links) - elif isinstance(extra_links, list): - note_links.extend(extra_links) - - recipient_links = [{"type": "HumanUser", "id": uid} for uid in to_users] - cc_links = [{"type": "HumanUser", "id": uid} for uid in cc_users] + extra = self._convert_entities_to_sg_links(links) + if isinstance(extra, dict): + note_links.append(extra) + elif isinstance(extra, list): + note_links.extend(extra) note_data = { "project": project, "subject": subject, "content": content, "note_links": note_links, - "addressings_to": recipient_links, - "addressings_cc": cc_links, + "addressings_to": [{"type": "HumanUser", "id": uid} for uid in to_users], + "addressings_cc": [{"type": "HumanUser", "id": uid} for uid in cc_users], } - # 4. Handle Author / Sudo - author_login = None - if author_email: - try: - author_user = self.get_user_by_email(author_email) - if author_user and author_user.login: - author_login = author_user.login - except ValueError as e: - # Wrap the ValueError in a specific UserNotFoundError - raise UserNotFoundError( - f"Author not found in ShotGrid: {author_email}" - ) from e - - if author_login: - with self.sudo(author_login): - result = self._sg.create("Note", note_data) - else: + # In user-token mode ShotGrid auto-records the authenticated user as author. + # In script-auth mode, use sudo to record the correct author. + if self.user_token: result = self._sg.create("Note", note_data) + else: + author_login = None + if author_email: + try: + author_user = self.get_user_by_email(author_email) + author_login = author_user.login if author_user else None + except ValueError as e: + raise UserNotFoundError(f"Author not found in ShotGrid: {author_email}") from e + if author_login: + with self.sudo(author_login): + result = self._sg.create("Note", note_data) + else: + result = self._sg.create("Note", note_data) if version_status: self._sg.update("Version", version_id, {"sg_status_list": version_status}) - return result["id"] - def attach_file_to_note( - self, note_id: int, file_path: str, display_name: str - ) -> bool: - """Upload a local file as an attachment on an existing ShotGrid note.""" + def attach_file_to_note(self, note_id: int, file_path: str, display_name: str) -> bool: if not self._sg: return False try: self._sg.upload( - "Note", - note_id, - file_path, - field_name="attachments", - display_name=display_name, + "Note", note_id, file_path, + field_name="attachments", display_name=display_name ) return True except Exception: @@ -1060,7 +854,6 @@ def update_transcript( def _get_dna_entity_type(sg_entity_type: str) -> str: - """Get the DNA entity type from the ShotGrid entity type.""" for entity_type, entity_data in FIELD_MAPPING.items(): if entity_data["entity_id"] == sg_entity_type: return entity_type diff --git a/backend/src/main.py b/backend/src/main.py index fe9304b8..17b9b0c6 100644 --- a/backend/src/main.py +++ b/backend/src/main.py @@ -8,6 +8,10 @@ from pathlib import Path from typing import Annotated, Optional, cast +import asyncio + +from pydantic import BaseModel + from fastapi import ( Depends, FastAPI, @@ -208,15 +212,31 @@ async def add_security_headers(request: Request, call_next): return response + +class LoginRequest(BaseModel): + """Credentials for standalone ShotGrid login (fallback path). + + Cloud ShotGrid: username = SG username, password = Legacy Login password. + Both username AND a Personal Access Token (PAT) bound to the account + are required on cloud sites. PATs cannot be admin-provisioned; each user + must generate one at profile.autodesk.com. + + On-prem Docker (SG_SITE_TYPE=onprem): PAT not required. + Use actual ShotGrid or LDAP/AD password. + """ + username: str + password: str + + # ----------------------------------------------------------------------------- # Dependencies # ----------------------------------------------------------------------------- -@lru_cache -def get_prodtrack_provider_cached() -> ProdtrackProviderBase: - """Get or create the production tracking provider singleton.""" - return get_prodtrack_provider() +# @lru_cache +# def get_prodtrack_provider_cached() -> ProdtrackProviderBase: +# """Get or create the production tracking provider singleton.""" +# return get_prodtrack_provider() @lru_cache @@ -237,9 +257,9 @@ def get_llm_provider_cached() -> LLMProviderBase: return get_llm_provider() -ProdtrackProviderDep = Annotated[ - ProdtrackProviderBase, Depends(get_prodtrack_provider_cached) -] +# ProdtrackProviderDep = Annotated[ +# ProdtrackProviderBase, Depends(get_prodtrack_provider_cached) +# ] StorageProviderDep = Annotated[ StorageProviderBase, Depends(get_storage_provider_cached) @@ -328,6 +348,61 @@ async def get_current_user( CurrentUserDep = Annotated[str, Depends(get_current_user)] +# ----------------------------------------------------------------------------- +# Production Tracking — per-request, user-scoped provider +# Must be defined AFTER CurrentUserDep +# ----------------------------------------------------------------------------- + + +async def get_user_scoped_prodtrack_provider( + _current_user: CurrentUserDep, + credentials: Annotated[ + HTTPAuthorizationCredentials | None, Depends(security) + ] = None, + auth_provider: AuthProviderDep = None, +) -> ProdtrackProviderBase: + """Return ShotgridProvider scoped to the authenticated user's SG token.""" + sg_token: Optional[str] = None + session_id: Optional[str] = None + + if credentials is not None and auth_provider is not None: + try: + from dna.auth_providers.shotgrid_sso import ShotGridSSOProvider + if isinstance(auth_provider, ShotGridSSOProvider): + session = auth_provider.get_session_for_request(credentials.credentials) + session_id = session.session_id + if session.sg_token: + sg_token = session.sg_token + except ValueError as exc: + raise HTTPException( + status_code=401, detail=str(exc), + headers={"WWW-Authenticate": "Bearer"}, + ) + + try: + return get_prodtrack_provider(user_token=sg_token, session_id=session_id) + except ValueError as exc: + raise HTTPException(status_code=503, detail=str(exc)) + + +async def _periodic_pool_cleanup() -> None: + """Evict idle SG connections from the pool every 5 minutes.""" + while True: + await asyncio.sleep(300) + try: + from dna.auth.connection_pool import get_connection_pool + evicted = get_connection_pool().cleanup_idle() + if evicted: + print(f"[pool] Evicted {evicted} idle SG connection(s).") + except Exception as exc: + print(f"[pool] Cleanup error: {exc}") + + +ProdtrackProviderDep = Annotated[ + ProdtrackProviderBase, Depends(get_user_scoped_prodtrack_provider) +] + + # ----------------------------------------------------------------------------- # Lifecycle events # ----------------------------------------------------------------------------- @@ -343,6 +418,7 @@ async def startup_event(): if callable(ensure_indexes): await ensure_indexes() await service.resubscribe_to_active_meetings() + asyncio.create_task(_periodic_pool_cleanup()) @app.on_event("shutdown") @@ -398,6 +474,113 @@ async def test_broadcast_transcript(payload: dict) -> dict: publisher = get_event_publisher() await publisher.ws_manager.broadcast(payload) return {"broadcasted": True, "clients": publisher.ws_manager.connection_count} +# ----------------------------------------------------------------------------- +# Auth endpoints +# ----------------------------------------------------------------------------- + + +@app.get("/auth/login", tags=["Auth"], summary="Get login mode — pat or sso") +async def auth_get_login_info(auth_provider: AuthProviderDep = None): + """Return the configured auth mode so the frontend can render the correct login UI. + + Returns: + {"mode": "pat"} for username+password login, or + {"mode": "sso", "redirect_url": "..."} for ShotGrid login page redirect. + {"mode": "none"} when AUTH_PROVIDER=none (development). + """ + if auth_provider is None: + return {"mode": "none"} + try: + from dna.auth_providers.shotgrid_sso import ShotGridSSOProvider + if isinstance(auth_provider, ShotGridSSOProvider): + return auth_provider.get_login_info() + except Exception as exc: + raise HTTPException(status_code=500, detail=str(exc)) + return {"mode": "none"} + + + +@app.post("/auth/login", tags=["Auth"], summary="Standalone login — ShotGrid username + Legacy Password") +async def auth_login(body: LoginRequest, auth_provider: AuthProviderDep): + """Login with ShotGrid username + legacy password.""" + if auth_provider is None: + return {"message": "Authentication disabled (AUTH_PROVIDER=none)"} + try: + from dna.auth_providers.shotgrid_sso import ShotGridSSOProvider + if not isinstance(auth_provider, ShotGridSSOProvider): + return {"message": f"Provider '{os.getenv('AUTH_PROVIDER', 'none')}': supply Bearer token directly."} + return auth_provider.login(username=body.username, password=body.password) + except ValueError as exc: + raise HTTPException(status_code=401, detail=str(exc)) + + + +@app.post("/auth/refresh", tags=["Auth"], summary="Refresh access token") +async def auth_refresh( + credentials: Annotated[HTTPAuthorizationCredentials | None, Depends(security)] = None, + auth_provider: AuthProviderDep = None, +): + """Refresh the DNA JWT using the stored ShotGrid refresh_token.""" + if credentials is None: + raise HTTPException(status_code=401, detail="Missing Authorization header.", headers={"WWW-Authenticate": "Bearer"}) + try: + from dna.auth_providers.shotgrid_sso import ShotGridSSOProvider + except ImportError: + raise HTTPException(status_code=500, detail="ShotGrid SSO provider unavailable.") + if not isinstance(auth_provider, ShotGridSSOProvider): + raise HTTPException(status_code=400, detail="Token refresh requires AUTH_PROVIDER=shotgrid.") + try: + return auth_provider.refresh_access_token(credentials.credentials) + except ValueError as exc: + raise HTTPException(status_code=401, detail=str(exc)) + + +@app.post("/auth/logout", tags=["Auth"], summary="Logout — revoke token and delete session") +async def auth_logout( + credentials: Annotated[HTTPAuthorizationCredentials | None, Depends(security)] = None, + auth_provider: AuthProviderDep = None, + _: CurrentUserDep = None, +): + """Revoke JWT and destroy server-side session.""" + if credentials and auth_provider: + try: + from dna.auth_providers.shotgrid_sso import ShotGridSSOProvider + if isinstance(auth_provider, ShotGridSSOProvider): + auth_provider.revoke_token(credentials.credentials) + except Exception as exc: + # Log but do not surface to the caller — logout must always succeed + # from the client's perspective so the browser clears its token. + import warnings + warnings.warn(f"[auth_logout] Token revocation error (non-fatal): {exc}", stacklevel=2) + return {"message": "Logged out successfully.", "action": "delete_token"} + + +@app.get("/auth/me", tags=["Auth"], summary="Get current user info") +async def auth_me( + current_user: CurrentUserDep, + credentials: Annotated[HTTPAuthorizationCredentials | None, Depends(security)] = None, + auth_provider: AuthProviderDep = None, +): + """Return information about the currently authenticated user.""" + response: dict = {"email": current_user} + if credentials and auth_provider: + try: + from dna.auth_providers.shotgrid_sso import ShotGridSSOProvider + if isinstance(auth_provider, ShotGridSSOProvider): + session = auth_provider.get_session_for_request(credentials.credentials) + response["name"] = session.name + response["shotgrid_user_id"] = session.sg_user_id + except ValueError as exc: + # Session is missing from MongoDB (e.g. after backend restart). + # Raise 401 so the frontend clears the stale token and shows + # the login page — instead of letting the user reach the app + # with a dead session and seeing 401 on every API call. + raise HTTPException( + status_code=401, + detail=str(exc), + headers={"WWW-Authenticate": "Bearer"}, + ) + return response MOCK_THUMBNAILS_DIR = ( diff --git a/backend/tests/test_auth_prod.py b/backend/tests/test_auth_prod.py new file mode 100644 index 00000000..e60b8b1d --- /dev/null +++ b/backend/tests/test_auth_prod.py @@ -0,0 +1,533 @@ +"""Production auth tests — session store, connection pool, ShotGrid SSO. + +Run with: + pytest tests/test_auth_prod.py -v + +Requires: + pip install fakeredis pytest pytest-asyncio +""" + +from __future__ import annotations + +import hashlib +import time +import uuid +from unittest.mock import MagicMock, patch + +import pytest + +try: + import fakeredis + HAS_FAKEREDIS = True +except ImportError: + HAS_FAKEREDIS = False + +# ─────────────────────────────────────────────────────────────────────────── # +# Helpers # +# ─────────────────────────────────────────────────────────────────────────── # + + +def _make_session(**kwargs) -> "UserSession": + from dna.auth.session_store import UserSession + defaults = dict( + session_id=str(uuid.uuid4()), + jti=str(uuid.uuid4()), + email="jane@studio.com", + name="Jane Artist", + sg_user_id=42, + sg_token="opaque-sg-token", + refresh_token="autodesk-refresh-token", + ) + defaults.update(kwargs) + return UserSession(**defaults) + + +def _make_store(ttl=3600) -> "SessionStore": + """Return a SessionStore backed by fakeredis (in-memory).""" + from dna.auth.session_store import SessionStore + store = SessionStore.__new__(SessionStore) + store.session_ttl = ttl + store.state_ttl = 600 + store._client = fakeredis.FakeRedis(decode_responses=True) + store._redis_url = "fake://" + return store + + +# ═══════════════════════════════════════════════════════════════════════════ # +# SessionStore tests # +# ═══════════════════════════════════════════════════════════════════════════ # + + +@pytest.mark.skipif(not HAS_FAKEREDIS, reason="fakeredis not installed") +class TestSessionStore: + + def test_create_and_get_session(self): + store = _make_store() + session = _make_session() + store.create_session(session) + retrieved = store.get_session(session.session_id) + assert retrieved is not None + assert retrieved.email == "jane@studio.com" + assert retrieved.sg_token == "opaque-sg-token" + assert retrieved.sg_user_id == 42 + + def test_get_missing_session_returns_none(self): + store = _make_store() + assert store.get_session("does-not-exist") is None + + def test_delete_session(self): + store = _make_store() + session = _make_session() + store.create_session(session) + store.delete_session(session.session_id) + assert store.get_session(session.session_id) is None + + def test_update_session_replaces_values(self): + store = _make_store() + session = _make_session() + store.create_session(session) + session.sg_token = "new-sg-token" + session.refresh_token = "new-refresh-token" + store.update_session(session) + updated = store.get_session(session.session_id) + assert updated.sg_token == "new-sg-token" + assert updated.refresh_token == "new-refresh-token" + + def test_revoke_and_check_token(self): + store = _make_store() + jti = str(uuid.uuid4()) + assert not store.is_token_revoked(jti) + store.revoke_token(jti, remaining_ttl_seconds=3600) + assert store.is_token_revoked(jti) + + def test_revoke_zero_ttl_not_stored(self): + store = _make_store() + jti = str(uuid.uuid4()) + store.revoke_token(jti, remaining_ttl_seconds=0) + assert not store.is_token_revoked(jti) + + def test_oauth_state_stored_and_consumed(self): + from dna.auth.session_store import OAuthState + store = _make_store() + state = "random-state-abc" + oauth_state = OAuthState( + code_verifier="verifier-xyz", + redirect_uri="http://localhost:3000/callback", + ) + store.store_oauth_state(state, oauth_state) + consumed = store.consume_oauth_state(state) + assert consumed is not None + assert consumed.code_verifier == "verifier-xyz" + + def test_oauth_state_one_time_use(self): + from dna.auth.session_store import OAuthState + store = _make_store() + state = "one-time-state" + store.store_oauth_state(state, OAuthState(code_verifier="v", redirect_uri="u")) + store.consume_oauth_state(state) # First consume — should work + assert store.consume_oauth_state(state) is None # Second — should be None + + def test_consume_missing_state_returns_none(self): + store = _make_store() + assert store.consume_oauth_state("nonexistent-state") is None + + def test_sg_token_never_exposed_in_get_session(self): + """The sg_token is in the session dict — but it only goes to the server.""" + store = _make_store() + session = _make_session(sg_token="super-secret-sg-token") + store.create_session(session) + retrieved = store.get_session(session.session_id) + # The session IS accessible server-side (that's the point) + assert retrieved.sg_token == "super-secret-sg-token" + # But verify nothing in the serialised data leaks outside the class boundary + # (the JWT the client sees doesn't contain sg_token — tested in SSO tests) + raw = store._client.get(f"dna:session:{session.session_id}") + import json + data = json.loads(raw) + assert "sg_token" in data # server has it + # (The client JWT is minted separately without sg_token — see SSO tests) + + +# ═══════════════════════════════════════════════════════════════════════════ # +# Connection pool tests # +# ═══════════════════════════════════════════════════════════════════════════ # + + +class TestShotGridConnectionPool: + + def _make_pool(self, max_size=5, sg_url="https://test.shotgunstudio.com"): + from dna.auth.connection_pool import ShotGridConnectionPool + with patch.dict("os.environ", {"SHOTGRID_URL": sg_url}): + pool = ShotGridConnectionPool(max_size=max_size) + return pool + + @patch("dna.auth.connection_pool.Shotgun") + def test_get_creates_connection(self, mock_sg): + pool = self._make_pool() + session_id = str(uuid.uuid4()) + conn = pool.get(session_id=session_id, sg_token="tok-1") + mock_sg.assert_called_once_with("https://test.shotgunstudio.com", session_token="tok-1") + assert pool.size == 1 + + @patch("dna.auth.connection_pool.Shotgun") + def test_get_same_session_reuses_connection(self, mock_sg): + pool = self._make_pool() + sid = str(uuid.uuid4()) + conn1 = pool.get(sid, "tok-1") + conn2 = pool.get(sid, "tok-1") + # Shotgun() called only once (cache hit on second get) + assert mock_sg.call_count == 1 + assert conn1 is conn2 + assert pool.stats["hits"] == 1 + + @patch("dna.auth.connection_pool.Shotgun") + def test_stale_token_replaces_connection(self, mock_sg): + pool = self._make_pool() + sid = str(uuid.uuid4()) + pool.get(sid, "tok-old") + pool.get(sid, "tok-new") # Different token — stale + assert mock_sg.call_count == 2 # Two connections created + assert pool.size == 1 # Only one entry (replaced, not added) + + @patch("dna.auth.connection_pool.Shotgun") + def test_lru_eviction_at_max_size(self, mock_sg): + pool = self._make_pool(max_size=3) + sids = [str(uuid.uuid4()) for _ in range(4)] + for i, sid in enumerate(sids): + pool.get(sid, f"tok-{i}") + # Pool should only contain 3 entries (oldest evicted) + assert pool.size == 3 + + @patch("dna.auth.connection_pool.Shotgun") + def test_release_removes_entry(self, mock_sg): + pool = self._make_pool() + sid = str(uuid.uuid4()) + pool.get(sid, "tok-1") + assert pool.size == 1 + pool.release(sid) + assert pool.size == 0 + + @patch("dna.auth.connection_pool.Shotgun") + def test_cleanup_idle_evicts_old_entries(self, mock_sg): + from dna.auth.connection_pool import _PoolEntry + + pool = self._make_pool() + sid = str(uuid.uuid4()) + pool.get(sid, "tok-1") + # Manually set last_used to 2 hours ago + pool._pool[sid].last_used = time.monotonic() - 7200 + evicted = pool.cleanup_idle() + assert evicted == 1 + assert pool.size == 0 + + @patch("dna.auth.connection_pool.Shotgun") + def test_stats_hit_rate(self, mock_sg): + pool = self._make_pool() + sid = str(uuid.uuid4()) + pool.get(sid, "tok") # miss + pool.get(sid, "tok") # hit + pool.get(sid, "tok") # hit + stats = pool.stats + assert stats["hits"] == 2 + assert stats["misses"] == 1 + assert stats["hit_rate"] == pytest.approx(2 / 3, rel=0.01) + + def test_missing_sg_url_raises(self): + from dna.auth.connection_pool import ShotGridConnectionPool + import os + with patch.dict(os.environ, {}, clear=True): + os.environ.pop("SHOTGRID_URL", None) + with pytest.raises(ValueError, match="SHOTGRID_URL"): + ShotGridConnectionPool() + + +# ═══════════════════════════════════════════════════════════════════════════ # +# SGToken helpers tests # +# ═══════════════════════════════════════════════════════════════════════════ # + + +class TestSGTokenSet: + """Tests for SGTokenSet token lifecycle helpers.""" + + def test_should_refresh_when_near_expiry(self): + from dna.auth.shotgrid_auth_client import SGTokenSet, ShotGridAuthClient + import time + + token = SGTokenSet( + access_token="tok", + refresh_token="ref", + token_type="Bearer", + expires_in=600, + obtained_at=time.time() - 500, # obtained 500s ago, expires in 100s + ) + # TTL buffer is 120s — 100s remaining < 120s buffer → should refresh + assert ShotGridAuthClient.should_refresh(token) is True + + def test_should_not_refresh_when_fresh(self): + from dna.auth.shotgrid_auth_client import SGTokenSet, ShotGridAuthClient + import time + + token = SGTokenSet( + access_token="tok", + refresh_token="ref", + token_type="Bearer", + expires_in=3600, + obtained_at=time.time() - 10, # obtained 10s ago, 3590s remaining + ) + assert ShotGridAuthClient.should_refresh(token) is False + + def test_is_expired_when_past_lifetime(self): + from dna.auth.shotgrid_auth_client import SGTokenSet, ShotGridAuthClient + import time + + token = SGTokenSet( + access_token="tok", + refresh_token="ref", + token_type="Bearer", + expires_in=600, + obtained_at=time.time() - 700, # 700s ago, expired 100s ago + ) + assert ShotGridAuthClient.is_expired(token) is True + + def test_is_not_expired_when_within_lifetime(self): + from dna.auth.shotgrid_auth_client import SGTokenSet, ShotGridAuthClient + import time + + token = SGTokenSet( + access_token="tok", + refresh_token="ref", + token_type="Bearer", + expires_in=3600, + obtained_at=time.time() - 100, + ) + assert ShotGridAuthClient.is_expired(token) is False + + +# ═══════════════════════════════════════════════════════════════════════════ # +# ShotGridSSOProvider tests # +# ═══════════════════════════════════════════════════════════════════════════ # + + +@pytest.mark.skipif(not HAS_FAKEREDIS, reason="fakeredis not installed") +class TestShotGridSSOProvider: + + def _make_provider(self, secret="test-secret-key-32-chars-minimum!!"): + import os + from unittest.mock import MagicMock + with patch.dict(os.environ, { + "JWT_SECRET_KEY": secret, + "JWT_ALGORITHM": "HS256", + "JWT_EXPIRE_MINUTES": "60", + "SHOTGRID_URL": "https://test.shotgunstudio.com", + }): + from dna.auth_providers.shotgrid_sso import ShotGridSSOProvider + provider = ShotGridSSOProvider.__new__(ShotGridSSOProvider) + provider._secret = secret + provider._algorithm = "HS256" + provider._expire_seconds = 3600 + provider._sessions = _make_store() + provider._sg_auth = MagicMock() + return provider + + def test_jwt_does_not_contain_sg_token(self): + """Critical: the SG token must NEVER appear in the client JWT.""" + import jwt as pyjwt + + provider = self._make_provider() + + # Simulate handle_callback internals + session_id = str(uuid.uuid4()) + jti = str(uuid.uuid4()) + session = _make_session( + session_id=session_id, jti=jti, sg_token="SECRET-SG-TOKEN" + ) + provider._sessions.create_session(session) + + token = provider._mint_jwt( + jti=jti, session_id=session_id, + email="jane@studio.com", name="Jane", sg_user_id=42 + ) + claims = pyjwt.decode( + token, provider._secret, algorithms=["HS256"] + ) + + # Must have session_id (server lookup key) + assert "session_id" in claims + # Must NOT have sg_token + assert "sg_token" not in claims + # Must NOT have refresh_token + assert "refresh_token" not in claims + + def test_validate_token_checks_blocklist(self): + provider = self._make_provider() + session_id = str(uuid.uuid4()) + jti = str(uuid.uuid4()) + session = _make_session(session_id=session_id, jti=jti) + provider._sessions.create_session(session) + token = provider._mint_jwt( + jti=jti, session_id=session_id, + email="jane@studio.com", name="Jane", sg_user_id=42 + ) + # Token should be valid before revocation + claims = provider.validate_token(token) + assert claims["email"] == "jane@studio.com" + + # Revoke it + provider._sessions.revoke_token(jti, 3600) + with pytest.raises(ValueError, match="revoked"): + provider.validate_token(token) + + def test_revoke_token_deletes_session(self): + provider = self._make_provider() + session_id = str(uuid.uuid4()) + jti = str(uuid.uuid4()) + session = _make_session(session_id=session_id, jti=jti) + provider._sessions.create_session(session) + token = provider._mint_jwt( + jti=jti, session_id=session_id, + email="jane@studio.com", name="Jane", sg_user_id=42 + ) + provider.revoke_token(token) + assert provider._sessions.get_session(session_id) is None + assert provider._sessions.is_token_revoked(jti) + + def test_refresh_within_grace_period(self): + import jwt as pyjwt + from dna.auth.shotgrid_auth_client import SGTokenSet + + provider = self._make_provider() + session_id = str(uuid.uuid4()) + old_jti = str(uuid.uuid4()) + session = _make_session( + session_id=session_id, jti=old_jti, + refresh_token="autodesk-refresh-tok" + ) + provider._sessions.create_session(session) + + # Mint a token that expired 30s ago (within 60s grace) + payload = { + "jti": old_jti, + "sub": "42", + "session_id": session_id, + "email": "jane@studio.com", + "iat": int(time.time()) - 3630, + "exp": int(time.time()) - 30, # 30s ago — within grace + } + expired_token = pyjwt.encode(payload, provider._secret, algorithm="HS256") + + # Mock Autodesk refresh + + provider._sg_auth.refresh_tokens.return_value = SGTokenSet( + access_token="new-sg-tok", + refresh_token="new-refresh-tok", + token_type="Bearer", + expires_in=3600, + ) + + result = provider.refresh_access_token(expired_token) + assert "access_token" in result + assert result["token_type"] == "Bearer" + + # Old jti should now be revoked + assert provider._sessions.is_token_revoked(old_jti) + + # Session should have new tokens + updated = provider._sessions.get_session(session_id) + assert updated.sg_token == "new-sg-tok" + assert updated.refresh_token == "new-refresh-tok" + + def test_refresh_beyond_grace_period_raises(self): + import jwt as pyjwt + + provider = self._make_provider() + session_id = str(uuid.uuid4()) + jti = str(uuid.uuid4()) + payload = { + "jti": jti, + "session_id": session_id, + "email": "jane@studio.com", + "exp": int(time.time()) - 200, # 200s ago — beyond 60s grace + } + stale_token = pyjwt.encode(payload, provider._secret, algorithm="HS256") + with pytest.raises(ValueError, match="too long"): + provider.refresh_access_token(stale_token) + + def test_get_session_for_request_returns_session(self): + provider = self._make_provider() + session_id = str(uuid.uuid4()) + jti = str(uuid.uuid4()) + session = _make_session(session_id=session_id, jti=jti) + provider._sessions.create_session(session) + token = provider._mint_jwt( + jti=jti, session_id=session_id, + email="jane@studio.com", name="Jane", sg_user_id=42 + ) + retrieved_session = provider.get_session_for_request(token) + assert retrieved_session.sg_token == "opaque-sg-token" + assert retrieved_session.email == "jane@studio.com" + + def test_get_session_for_revoked_token_raises(self): + provider = self._make_provider() + session_id = str(uuid.uuid4()) + jti = str(uuid.uuid4()) + session = _make_session(session_id=session_id, jti=jti) + provider._sessions.create_session(session) + token = provider._mint_jwt( + jti=jti, session_id=session_id, + email="jane@studio.com", name="Jane", sg_user_id=42 + ) + provider._sessions.revoke_token(jti, 3600) + with pytest.raises(ValueError, match="revoked"): + provider.get_session_for_request(token) + + +# ═══════════════════════════════════════════════════════════════════════════ # +# ShotgridProvider user-token mode tests # +# ═══════════════════════════════════════════════════════════════════════════ # + + +class TestShotgridProviderProductionMode: + + @patch("dna.auth.connection_pool.Shotgun") + @patch("dna.prodtrack_providers.shotgrid.Shotgun") + def test_user_token_with_session_uses_pool(self, mock_sg_direct, mock_sg_pool): + import os + from unittest.mock import patch as _patch + + with _patch.dict(os.environ, {"SHOTGRID_URL": "https://sg.example.com"}): + from dna.auth.connection_pool import ShotGridConnectionPool + pool = ShotGridConnectionPool() + + session_id = str(uuid.uuid4()) + sg_token = "user-session-token" + # Pre-populate pool with a mock connection + mock_conn = MagicMock() + pool._pool[session_id] = __import__( + "dna.auth.connection_pool", fromlist=["_PoolEntry"] + )._PoolEntry( + conn=mock_conn, + session_id=session_id, + sg_token_hash=hashlib.sha256(sg_token.encode()).hexdigest(), + ) + + with _patch("dna.auth.connection_pool.get_connection_pool", return_value=pool): + from dna.prodtrack_providers.shotgrid import ShotgridProvider + provider = ShotgridProvider( + user_token=sg_token, session_id=session_id + ) + # Should get connection from pool, not create new Shotgun() + assert provider.sg is mock_conn + mock_sg_direct.assert_not_called() + + @patch("dna.prodtrack_providers.shotgrid.Shotgun") + def test_sudo_in_user_token_mode_is_noop(self, mock_sg): + import os + with patch.dict(os.environ, {"SHOTGRID_URL": "https://sg.example.com"}): + from dna.prodtrack_providers.shotgrid import ShotgridProvider + provider = ShotgridProvider(user_token="tok") + original_conn = provider.sg + with provider.sudo("some-user"): + # In user-token mode, sudo should not change the connection + assert provider._sg is original_conn + # After context, still the same + assert provider._sg is original_conn diff --git a/frontend/Dockerfile b/frontend/Dockerfile index 566444df..aed8b917 100644 --- a/frontend/Dockerfile +++ b/frontend/Dockerfile @@ -19,14 +19,12 @@ COPY packages/app/ ./packages/app/ # Build arguments for API URLs and authentication (baked into the static build) ARG VITE_API_BASE_URL ARG VITE_WS_URL -ARG VITE_AUTH_PROVIDER=google -ARG VITE_GOOGLE_CLIENT_ID +ARG VITE_AUTH_PROVIDER=shotgrid # Set environment variables for build ENV VITE_API_BASE_URL=$VITE_API_BASE_URL ENV VITE_WS_URL=$VITE_WS_URL ENV VITE_AUTH_PROVIDER=$VITE_AUTH_PROVIDER -ENV VITE_GOOGLE_CLIENT_ID=$VITE_GOOGLE_CLIENT_ID # Build the application (vite handles transpilation, skip tsc type checking) WORKDIR /app/packages/app diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 7e07b2a1..bc2e8779 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -16,6 +16,7 @@ "@tiptap/suggestion": "^3.20.1" }, "devDependencies": { + "@vitejs/plugin-react": "^4.7.0", "prettier": "^3.4.2", "typescript": "^5.9.3" }, @@ -30,15 +31,13 @@ "version": "4.4.4", "resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.4.4.tgz", "integrity": "sha512-Elp+iwUx5rN5+Y8xLt5/GRoG20WGoDCQ/1Fb+1LiGtvwbDavuSk0jhD/eZdckHAuzcDzccnkv+rEjyWfRx18gg==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/@ampproject/remapping": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", "dev": true, - "license": "Apache-2.0", "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.24" @@ -52,7 +51,6 @@ "resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-3.2.0.tgz", "integrity": "sha512-K1A6z8tS3XsmCMM86xoWdn7Fkdn9m6RSVtocUrJYIwZnFVkng/PvkEoWtOWmP+Scc6saYWHWZYbndEEXxl24jw==", "dev": true, - "license": "MIT", "dependencies": { "@csstools/css-calc": "^2.1.3", "@csstools/css-color-parser": "^3.0.9", @@ -61,13 +59,17 @@ "lru-cache": "^10.4.3" } }, + "node_modules/@asamuzakjp/css-color/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 + }, "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", - "peer": true, "dependencies": { "@babel/helper-validator-identifier": "^7.28.5", "js-tokens": "^4.0.0", @@ -77,12 +79,130 @@ "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, + "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, + "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, + "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, + "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, + "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, + "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, + "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-plugin-utils": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.28.6.tgz", + "integrity": "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==", + "dev": true, + "engines": { + "node": ">=6.9.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" } @@ -92,17 +212,37 @@ "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, + "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, + "dependencies": { + "@babel/template": "^7.28.6", + "@babel/types": "^7.29.0" + }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/parser": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.0.tgz", - "integrity": "sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww==", + "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" }, @@ -113,11 +253,72 @@ "node": ">=6.0.0" } }, + "node_modules/@babel/plugin-transform-react-jsx-self": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.27.1.tgz", + "integrity": "sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-source": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.27.1.tgz", + "integrity": "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, "node_modules/@babel/runtime": { + "version": "7.29.2", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.2.tgz", + "integrity": "sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g==", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/template": { "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.6.tgz", - "integrity": "sha512-05WQkdpL9COIMz4LjTxGpPNCdlpyimKppYNoJ5Di5EUObifl8t4tuLuUBBZEpoLYOmfvIWrsp9fCl0HoPRVTdA==", - "license": "MIT", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz", + "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==", + "dev": true, + "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, + "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" } @@ -127,7 +328,6 @@ "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" @@ -140,8 +340,7 @@ "version": "0.2.3", "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/@csstools/color-helpers": { "version": "5.1.0", @@ -158,7 +357,6 @@ "url": "https://opencollective.com/csstools" } ], - "license": "MIT-0", "engines": { "node": ">=18" } @@ -178,7 +376,6 @@ "url": "https://opencollective.com/csstools" } ], - "license": "MIT", "engines": { "node": ">=18" }, @@ -202,7 +399,6 @@ "url": "https://opencollective.com/csstools" } ], - "license": "MIT", "dependencies": { "@csstools/color-helpers": "^5.1.0", "@csstools/css-calc": "^2.1.4" @@ -230,7 +426,6 @@ "url": "https://opencollective.com/csstools" } ], - "license": "MIT", "engines": { "node": ">=18" }, @@ -253,7 +448,6 @@ "url": "https://opencollective.com/csstools" } ], - "license": "MIT", "engines": { "node": ">=18" } @@ -270,7 +464,6 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.4.0.tgz", "integrity": "sha512-QgD4fyscGcbbKwJmqNvUMSE02OsHUa+lAWKdEUIJKgqe5IwRSKd7+KhibEWdaKwgjLj0DRSHA9biAIqGBk05lw==", - "license": "MIT", "dependencies": { "@emotion/memoize": "^0.9.0" } @@ -278,14 +471,7 @@ "node_modules/@emotion/memoize": { "version": "0.9.0", "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.9.0.tgz", - "integrity": "sha512-30FAj7/EoJ5mwVPOWhAyCX+FPfMDrVecJAM+Iw9NRoSl4BBAQeqj4cApHHUXOVvIPgLVDsCFoz/hGD+5QQD1GQ==", - "license": "MIT" - }, - "node_modules/@emotion/unitless": { - "version": "0.10.0", - "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.10.0.tgz", - "integrity": "sha512-dFoMUuQA20zvtVTuxZww6OHoJYgrzfKM1t52mVySDJnMSEa08ruEvdYQbhvyu6soU+NeLVd3yKfTfT0NeV6qGg==", - "license": "MIT" + "integrity": "sha512-30FAj7/EoJ5mwVPOWhAyCX+FPfMDrVecJAM+Iw9NRoSl4BBAQeqj4cApHHUXOVvIPgLVDsCFoz/hGD+5QQD1GQ==" }, "node_modules/@esbuild/aix-ppc64": { "version": "0.21.5", @@ -295,11 +481,11 @@ "ppc64" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "aix" ], + "peer": true, "engines": { "node": ">=12" } @@ -312,11 +498,11 @@ "arm" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "android" ], + "peer": true, "engines": { "node": ">=12" } @@ -329,11 +515,11 @@ "arm64" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "android" ], + "peer": true, "engines": { "node": ">=12" } @@ -346,11 +532,11 @@ "x64" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "android" ], + "peer": true, "engines": { "node": ">=12" } @@ -363,11 +549,11 @@ "arm64" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "darwin" ], + "peer": true, "engines": { "node": ">=12" } @@ -380,11 +566,11 @@ "x64" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "darwin" ], + "peer": true, "engines": { "node": ">=12" } @@ -397,11 +583,11 @@ "arm64" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "freebsd" ], + "peer": true, "engines": { "node": ">=12" } @@ -414,11 +600,11 @@ "x64" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "freebsd" ], + "peer": true, "engines": { "node": ">=12" } @@ -431,11 +617,11 @@ "arm" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "linux" ], + "peer": true, "engines": { "node": ">=12" } @@ -448,11 +634,11 @@ "arm64" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "linux" ], + "peer": true, "engines": { "node": ">=12" } @@ -465,11 +651,11 @@ "ia32" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "linux" ], + "peer": true, "engines": { "node": ">=12" } @@ -482,11 +668,11 @@ "loong64" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "linux" ], + "peer": true, "engines": { "node": ">=12" } @@ -499,11 +685,11 @@ "mips64el" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "linux" ], + "peer": true, "engines": { "node": ">=12" } @@ -516,11 +702,11 @@ "ppc64" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "linux" ], + "peer": true, "engines": { "node": ">=12" } @@ -533,11 +719,11 @@ "riscv64" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "linux" ], + "peer": true, "engines": { "node": ">=12" } @@ -550,11 +736,11 @@ "s390x" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "linux" ], + "peer": true, "engines": { "node": ">=12" } @@ -567,11 +753,11 @@ "x64" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "linux" ], + "peer": true, "engines": { "node": ">=12" } @@ -584,11 +770,11 @@ "x64" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "netbsd" ], + "peer": true, "engines": { "node": ">=12" } @@ -601,11 +787,11 @@ "x64" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "openbsd" ], + "peer": true, "engines": { "node": ">=12" } @@ -618,11 +804,11 @@ "x64" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "sunos" ], + "peer": true, "engines": { "node": ">=12" } @@ -635,11 +821,11 @@ "arm64" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "win32" ], + "peer": true, "engines": { "node": ">=12" } @@ -652,11 +838,11 @@ "ia32" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "win32" ], + "peer": true, "engines": { "node": ">=12" } @@ -669,11 +855,11 @@ "x64" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "win32" ], + "peer": true, "engines": { "node": ">=12" } @@ -683,7 +869,6 @@ "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz", "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==", "dev": true, - "license": "MIT", "dependencies": { "eslint-visitor-keys": "^3.4.3" }, @@ -702,7 +887,6 @@ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", "dev": true, - "license": "Apache-2.0", "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, @@ -715,21 +899,19 @@ "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", "dev": true, - "license": "MIT", "engines": { "node": "^12.0.0 || ^14.0.0 || >=16.0.0" } }, "node_modules/@eslint/config-array": { - "version": "0.21.1", - "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.1.tgz", - "integrity": "sha512-aw1gNayWpdI/jSYVgzN5pL0cfzU02GT3NBpeT/DXbx1/1x7ZKxFPd9bwrzygx/qiwIQiJ1sw/zD8qY/kRvlGHA==", + "version": "0.21.2", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.2.tgz", + "integrity": "sha512-nJl2KGTlrf9GjLimgIru+V/mzgSK0ABCDQRvxw5BjURL7WfH5uoWmizbH7QB6MmnMBd8cIC9uceWnezL1VZWWw==", "dev": true, - "license": "Apache-2.0", "dependencies": { "@eslint/object-schema": "^2.1.7", "debug": "^4.3.1", - "minimatch": "^3.1.2" + "minimatch": "^3.1.5" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -740,7 +922,6 @@ "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.4.2.tgz", "integrity": "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==", "dev": true, - "license": "Apache-2.0", "dependencies": { "@eslint/core": "^0.17.0" }, @@ -753,7 +934,6 @@ "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.17.0.tgz", "integrity": "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==", "dev": true, - "license": "Apache-2.0", "dependencies": { "@types/json-schema": "^7.0.15" }, @@ -762,20 +942,19 @@ } }, "node_modules/@eslint/eslintrc": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.3.tgz", - "integrity": "sha512-Kr+LPIUVKz2qkx1HAMH8q1q6azbqBAsXJUxBl/ODDuVPX45Z9DfwB8tPjTi6nNZ8BuM3nbJxC5zCAg5elnBUTQ==", + "version": "3.3.5", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.5.tgz", + "integrity": "sha512-4IlJx0X0qftVsN5E+/vGujTRIFtwuLbNsVUe7TO6zYPDR1O6nFwvwhIKEKSrl6dZchmYBITazxKoUYOjdtjlRg==", "dev": true, - "license": "MIT", "dependencies": { - "ajv": "^6.12.4", + "ajv": "^6.14.0", "debug": "^4.3.2", "espree": "^10.0.1", "globals": "^14.0.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", "js-yaml": "^4.1.1", - "minimatch": "^3.1.2", + "minimatch": "^3.1.5", "strip-json-comments": "^3.1.1" }, "engines": { @@ -790,7 +969,6 @@ "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", "dev": true, - "license": "MIT", "engines": { "node": ">=18" }, @@ -799,11 +977,10 @@ } }, "node_modules/@eslint/js": { - "version": "9.39.2", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.2.tgz", - "integrity": "sha512-q1mjIoW1VX4IvSocvM/vbTiveKC4k9eLrajNEuSsmjymSDEbpGddtpfOoN7YGAqBK3NG+uqo8ia4PDTt8buCYA==", + "version": "9.39.4", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.4.tgz", + "integrity": "sha512-nE7DEIchvtiFTwBw4Lfbu59PG+kCofhjsKaCWzxTpt4lfRjRMqG6uMBzKXuEcyXhOHoUp9riAm7/aWYGhXZ9cw==", "dev": true, - "license": "MIT", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, @@ -816,7 +993,6 @@ "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.7.tgz", "integrity": "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==", "dev": true, - "license": "Apache-2.0", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } @@ -826,7 +1002,6 @@ "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.4.1.tgz", "integrity": "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==", "dev": true, - "license": "Apache-2.0", "dependencies": { "@eslint/core": "^0.17.0", "levn": "^0.4.1" @@ -836,31 +1011,28 @@ } }, "node_modules/@floating-ui/core": { - "version": "1.7.4", - "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.7.4.tgz", - "integrity": "sha512-C3HlIdsBxszvm5McXlB8PeOEWfBhcGBTZGkGlWc2U0KFY5IwG5OQEuQ8rq52DZmcHDlPLd+YFBK+cZcytwIFWg==", - "license": "MIT", + "version": "1.7.5", + "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.7.5.tgz", + "integrity": "sha512-1Ih4WTWyw0+lKyFMcBHGbb5U5FtuHJuujoyyr5zTaWS5EYMeT6Jb2AuDeftsCsEuchO+mM2ij5+q9crhydzLhQ==", "dependencies": { - "@floating-ui/utils": "^0.2.10" + "@floating-ui/utils": "^0.2.11" } }, "node_modules/@floating-ui/dom": { - "version": "1.7.5", - "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.7.5.tgz", - "integrity": "sha512-N0bD2kIPInNHUHehXhMke1rBGs1dwqvC9O9KYMyyjK7iXt7GAhnro7UlcuYcGdS/yYOlq0MAVgrow8IbWJwyqg==", - "license": "MIT", + "version": "1.7.6", + "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.7.6.tgz", + "integrity": "sha512-9gZSAI5XM36880PPMm//9dfiEngYoC6Am2izES1FF406YFsjvyBMmeJ2g4SAju3xWwtuynNRFL2s9hgxpLI5SQ==", "dependencies": { - "@floating-ui/core": "^1.7.4", - "@floating-ui/utils": "^0.2.10" + "@floating-ui/core": "^1.7.5", + "@floating-ui/utils": "^0.2.11" } }, "node_modules/@floating-ui/react-dom": { - "version": "2.1.7", - "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.1.7.tgz", - "integrity": "sha512-0tLRojf/1Go2JgEVm+3Frg9A3IW8bJgKgdO0BN5RkF//ufuz2joZM63Npau2ff3J6lUVYgDSNzNkR+aH3IVfjg==", - "license": "MIT", + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.1.8.tgz", + "integrity": "sha512-cC52bHwM/n/CxS87FH0yWdngEZrjdtLW/qVruo68qg+prK7ZQ4YGdut2GyDVpoGeAYe/h899rVeOVm6Oi40k2A==", "dependencies": { - "@floating-ui/dom": "^1.7.5" + "@floating-ui/dom": "^1.7.6" }, "peerDependencies": { "react": ">=16.8.0", @@ -868,41 +1040,50 @@ } }, "node_modules/@floating-ui/utils": { - "version": "0.2.10", - "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.10.tgz", - "integrity": "sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==", - "license": "MIT" + "version": "0.2.11", + "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.11.tgz", + "integrity": "sha512-RiB/yIh78pcIxl6lLMG0CgBXAZ2Y0eVHqMPYugu+9U0AeT6YBeiJpf7lbdJNIugFP5SIjwNRgo4DhR1Qxi26Gg==" }, "node_modules/@humanfs/core": { - "version": "0.19.1", - "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", - "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.2.tgz", + "integrity": "sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA==", "dev": true, - "license": "Apache-2.0", + "dependencies": { + "@humanfs/types": "^0.15.0" + }, "engines": { "node": ">=18.18.0" } }, "node_modules/@humanfs/node": { - "version": "0.16.7", - "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.7.tgz", - "integrity": "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==", + "version": "0.16.8", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.8.tgz", + "integrity": "sha512-gE1eQNZ3R++kTzFUpdGlpmy8kDZD/MLyHqDwqjkVQI0JMdI1D51sy1H958PNXYkM2rAac7e5/CnIKZrHtPh3BQ==", "dev": true, - "license": "Apache-2.0", "dependencies": { - "@humanfs/core": "^0.19.1", + "@humanfs/core": "^0.19.2", + "@humanfs/types": "^0.15.0", "@humanwhocodes/retry": "^0.4.0" }, "engines": { "node": ">=18.18.0" } }, + "node_modules/@humanfs/types": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/@humanfs/types/-/types-0.15.0.tgz", + "integrity": "sha512-ZZ1w0aoQkwuUuC7Yf+7sdeaNfqQiiLcSRbfI08oAxqLtpXQr9AIVX7Ay7HLDuiLYAaFPu8oBYNq/QIi9URHJ3Q==", + "dev": true, + "engines": { + "node": ">=18.18.0" + } + }, "node_modules/@humanwhocodes/module-importer": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", "dev": true, - "license": "Apache-2.0", "engines": { "node": ">=12.22" }, @@ -916,7 +1097,6 @@ "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", "dev": true, - "license": "Apache-2.0", "engines": { "node": ">=18.18" }, @@ -930,7 +1110,6 @@ "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", @@ -944,11 +1123,10 @@ } }, "node_modules/@istanbuljs/schema": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", - "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.6.tgz", + "integrity": "sha512-+Sg6GCR/wy1oSmQDFq4LQDAhm3ETKnorxN+y5nbLULOR3P0c14f2Wurzj3/xqPXtasLFfHd5iRFQ7AJt4KH2cw==", "dev": true, - "license": "MIT", "engines": { "node": ">=8" } @@ -958,18 +1136,26 @@ "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, + "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" } @@ -978,15 +1164,13 @@ "version": "1.5.5", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", - "dev": true, - "license": "MIT" + "dev": true }, "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" @@ -995,15 +1179,13 @@ "node_modules/@mixmark-io/domino": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/@mixmark-io/domino/-/domino-2.2.0.tgz", - "integrity": "sha512-Y28PR25bHXUg88kCV7nivXrP2Nj2RueZ3/l/jdx6J9f8J4nsEGcgX0Qe6lt7Pa+J79+kPiJU3LguR6O/6zrLOw==", - "license": "BSD-2-Clause" + "integrity": "sha512-Y28PR25bHXUg88kCV7nivXrP2Nj2RueZ3/l/jdx6J9f8J4nsEGcgX0Qe6lt7Pa+J79+kPiJU3LguR6O/6zrLOw==" }, "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" @@ -1013,32 +1195,27 @@ "version": "1.0.0-next.29", "resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.29.tgz", "integrity": "sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/@radix-ui/colors": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/@radix-ui/colors/-/colors-3.0.0.tgz", - "integrity": "sha512-FUOsGBkHrYJwCSEtWRCIfQbZG7q1e6DgxCIOe1SUQzDe/7rXXeA47s8yCn6fuTNQAj1Zq4oTFi9Yjp3wzElcxg==", - "license": "MIT" + "integrity": "sha512-FUOsGBkHrYJwCSEtWRCIfQbZG7q1e6DgxCIOe1SUQzDe/7rXXeA47s8yCn6fuTNQAj1Zq4oTFi9Yjp3wzElcxg==" }, "node_modules/@radix-ui/number": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/@radix-ui/number/-/number-1.1.1.tgz", - "integrity": "sha512-MkKCwxlXTgz6CFoJx3pCwn07GKp36+aZyu/u2Ln2VrA5DcdyCZkASEDBTd8x5whTQQL5CiYf4prXKLcgQdv29g==", - "license": "MIT" + "integrity": "sha512-MkKCwxlXTgz6CFoJx3pCwn07GKp36+aZyu/u2Ln2VrA5DcdyCZkASEDBTd8x5whTQQL5CiYf4prXKLcgQdv29g==" }, "node_modules/@radix-ui/primitive": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.3.tgz", - "integrity": "sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg==", - "license": "MIT" + "integrity": "sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg==" }, "node_modules/@radix-ui/react-accessible-icon": { "version": "1.1.7", "resolved": "https://registry.npmjs.org/@radix-ui/react-accessible-icon/-/react-accessible-icon-1.1.7.tgz", "integrity": "sha512-XM+E4WXl0OqUJFovy6GjmxxFyx9opfCAIUku4dlKRd5YEPqt4kALOkQOp0Of6reHuUkJuiPBEc5k0o4z4lTC8A==", - "license": "MIT", "dependencies": { "@radix-ui/react-visually-hidden": "1.2.3" }, @@ -1061,7 +1238,6 @@ "version": "1.2.12", "resolved": "https://registry.npmjs.org/@radix-ui/react-accordion/-/react-accordion-1.2.12.tgz", "integrity": "sha512-T4nygeh9YE9dLRPhAHSeOZi7HBXo+0kYIPJXayZfvWOWA0+n3dESrZbjfDPUABkUNym6Hd+f2IR113To8D2GPA==", - "license": "MIT", "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-collapsible": "1.1.12", @@ -1092,7 +1268,6 @@ "version": "1.1.15", "resolved": "https://registry.npmjs.org/@radix-ui/react-alert-dialog/-/react-alert-dialog-1.1.15.tgz", "integrity": "sha512-oTVLkEw5GpdRe29BqJ0LSDFWI3qu0vR1M0mUkOQWDIUnY/QIkLpgDMWuKxP94c2NAC2LGcgVhG1ImF3jkZ5wXw==", - "license": "MIT", "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-compose-refs": "1.1.2", @@ -1120,7 +1295,6 @@ "version": "1.1.7", "resolved": "https://registry.npmjs.org/@radix-ui/react-arrow/-/react-arrow-1.1.7.tgz", "integrity": "sha512-F+M1tLhO+mlQaOWspE8Wstg+z6PwxwRd8oQ8IXceWz92kfAmalTRf0EjrouQeo7QssEPfCn05B4Ihs1K9WQ/7w==", - "license": "MIT", "dependencies": { "@radix-ui/react-primitive": "2.1.3" }, @@ -1143,7 +1317,6 @@ "version": "1.1.7", "resolved": "https://registry.npmjs.org/@radix-ui/react-aspect-ratio/-/react-aspect-ratio-1.1.7.tgz", "integrity": "sha512-Yq6lvO9HQyPwev1onK1daHCHqXVLzPhSVjmsNjCa2Zcxy2f7uJD2itDtxknv6FzAKCwD1qQkeVDmX/cev13n/g==", - "license": "MIT", "dependencies": { "@radix-ui/react-primitive": "2.1.3" }, @@ -1166,7 +1339,6 @@ "version": "1.1.10", "resolved": "https://registry.npmjs.org/@radix-ui/react-avatar/-/react-avatar-1.1.10.tgz", "integrity": "sha512-V8piFfWapM5OmNCXTzVQY+E1rDa53zY+MQ4Y7356v4fFz6vqCyUtIz2rUD44ZEdwg78/jKmMJHj07+C/Z/rcog==", - "license": "MIT", "dependencies": { "@radix-ui/react-context": "1.1.2", "@radix-ui/react-primitive": "2.1.3", @@ -1193,7 +1365,6 @@ "version": "1.3.3", "resolved": "https://registry.npmjs.org/@radix-ui/react-checkbox/-/react-checkbox-1.3.3.tgz", "integrity": "sha512-wBbpv+NQftHDdG86Qc0pIyXk5IR3tM8Vd0nWLKDcX8nNn4nXFOFwsKuqw2okA/1D/mpaAkmuyndrPJTYDNZtFw==", - "license": "MIT", "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-compose-refs": "1.1.2", @@ -1223,7 +1394,6 @@ "version": "1.1.12", "resolved": "https://registry.npmjs.org/@radix-ui/react-collapsible/-/react-collapsible-1.1.12.tgz", "integrity": "sha512-Uu+mSh4agx2ib1uIGPP4/CKNULyajb3p92LsVXmH2EHVMTfZWpll88XJ0j4W0z3f8NK1eYl1+Mf/szHPmcHzyA==", - "license": "MIT", "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-compose-refs": "1.1.2", @@ -1253,7 +1423,6 @@ "version": "1.1.7", "resolved": "https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-1.1.7.tgz", "integrity": "sha512-Fh9rGN0MoI4ZFUNyfFVNU4y9LUz93u9/0K+yLgA2bwRojxM8JU1DyvvMBabnZPBgMWREAJvU2jjVzq+LrFUglw==", - "license": "MIT", "dependencies": { "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", @@ -1279,7 +1448,6 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.2.tgz", "integrity": "sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg==", - "license": "MIT", "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" @@ -1294,7 +1462,6 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.2.tgz", "integrity": "sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA==", - "license": "MIT", "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" @@ -1309,7 +1476,6 @@ "version": "2.2.16", "resolved": "https://registry.npmjs.org/@radix-ui/react-context-menu/-/react-context-menu-2.2.16.tgz", "integrity": "sha512-O8morBEW+HsVG28gYDZPTrT9UUovQUlJue5YO836tiTJhuIWBm/zQHc7j388sHWtdH/xUZurK9olD2+pcqx5ww==", - "license": "MIT", "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-context": "1.1.2", @@ -1337,7 +1503,6 @@ "version": "1.1.15", "resolved": "https://registry.npmjs.org/@radix-ui/react-dialog/-/react-dialog-1.1.15.tgz", "integrity": "sha512-TCglVRtzlffRNxRMEyR36DGBLJpeusFcgMVD9PZEzAKnUs1lKCgX5u9BmC2Yg+LL9MgZDugFFs1Vl+Jp4t/PGw==", - "license": "MIT", "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-compose-refs": "1.1.2", @@ -1373,7 +1538,6 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/@radix-ui/react-direction/-/react-direction-1.1.1.tgz", "integrity": "sha512-1UEWRX6jnOA2y4H5WczZ44gOOjTEmlqv1uNW4GAJEO5+bauCBhv8snY65Iw5/VOS/ghKN9gr2KjnLKxrsvoMVw==", - "license": "MIT", "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" @@ -1388,7 +1552,6 @@ "version": "1.1.11", "resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.11.tgz", "integrity": "sha512-Nqcp+t5cTB8BinFkZgXiMJniQH0PsUt2k51FUhbdfeKvc4ACcG2uQniY/8+h1Yv6Kza4Q7lD7PQV0z0oicE0Mg==", - "license": "MIT", "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-compose-refs": "1.1.2", @@ -1415,7 +1578,6 @@ "version": "2.1.16", "resolved": "https://registry.npmjs.org/@radix-ui/react-dropdown-menu/-/react-dropdown-menu-2.1.16.tgz", "integrity": "sha512-1PLGQEynI/3OX/ftV54COn+3Sud/Mn8vALg2rWnBLnRaGtJDduNW/22XjlGgPdpcIbiQxjKtb7BkcjP00nqfJw==", - "license": "MIT", "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-compose-refs": "1.1.2", @@ -1444,7 +1606,6 @@ "version": "1.1.3", "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-guards/-/react-focus-guards-1.1.3.tgz", "integrity": "sha512-0rFg/Rj2Q62NCm62jZw0QX7a3sz6QCQU0LpZdNrJX8byRGaGVTqbrW9jAoIAHyMQqsNpeZ81YgSizOt5WXq0Pw==", - "license": "MIT", "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" @@ -1459,7 +1620,6 @@ "version": "1.1.7", "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-scope/-/react-focus-scope-1.1.7.tgz", "integrity": "sha512-t2ODlkXBQyn7jkl6TNaw/MtVEVvIGelJDCG41Okq/KwUsJBwQ4XVZsHAVUkK4mBv3ewiAS3PGuUWuY2BoK4ZUw==", - "license": "MIT", "dependencies": { "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-primitive": "2.1.3", @@ -1484,7 +1644,6 @@ "version": "0.1.8", "resolved": "https://registry.npmjs.org/@radix-ui/react-form/-/react-form-0.1.8.tgz", "integrity": "sha512-QM70k4Zwjttifr5a4sZFts9fn8FzHYvQ5PiB19O2HsYibaHSVt9fH9rzB0XZo/YcM+b7t/p7lYCT/F5eOeF5yQ==", - "license": "MIT", "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-compose-refs": "1.1.2", @@ -1512,7 +1671,6 @@ "version": "1.1.15", "resolved": "https://registry.npmjs.org/@radix-ui/react-hover-card/-/react-hover-card-1.1.15.tgz", "integrity": "sha512-qgTkjNT1CfKMoP0rcasmlH2r1DAiYicWsDsufxl940sT2wHNEWWv6FMWIQXWhVdmC1d/HYfbhQx60KYyAtKxjg==", - "license": "MIT", "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-compose-refs": "1.1.2", @@ -1543,7 +1701,6 @@ "version": "1.3.2", "resolved": "https://registry.npmjs.org/@radix-ui/react-icons/-/react-icons-1.3.2.tgz", "integrity": "sha512-fyQIhGDhzfc9pK2kH6Pl9c4BDJGfMkPqkyIgYDthyNYoNg3wVhoJMMh19WS4Up/1KMPFVpNsT2q3WmXn2N1m6g==", - "license": "MIT", "peerDependencies": { "react": "^16.x || ^17.x || ^18.x || ^19.0.0 || ^19.0.0-rc" } @@ -1552,7 +1709,6 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.1.1.tgz", "integrity": "sha512-kGkGegYIdQsOb4XjsfM97rXsiHaBwco+hFI66oO4s9LU+PLAC5oJ7khdOVFxkhsmlbpUqDAvXw11CluXP+jkHg==", - "license": "MIT", "dependencies": { "@radix-ui/react-use-layout-effect": "1.1.1" }, @@ -1570,7 +1726,6 @@ "version": "2.1.7", "resolved": "https://registry.npmjs.org/@radix-ui/react-label/-/react-label-2.1.7.tgz", "integrity": "sha512-YT1GqPSL8kJn20djelMX7/cTRp/Y9w5IZHvfxQTVHrOqa2yMl7i/UfMqKRU5V7mEyKTrUVgJXhNQPVCG8PBLoQ==", - "license": "MIT", "dependencies": { "@radix-ui/react-primitive": "2.1.3" }, @@ -1593,7 +1748,6 @@ "version": "2.1.16", "resolved": "https://registry.npmjs.org/@radix-ui/react-menu/-/react-menu-2.1.16.tgz", "integrity": "sha512-72F2T+PLlphrqLcAotYPp0uJMr5SjP5SL01wfEspJbru5Zs5vQaSHb4VB3ZMJPimgHHCHG7gMOeOB9H3Hdmtxg==", - "license": "MIT", "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-collection": "1.1.7", @@ -1633,7 +1787,6 @@ "version": "1.1.16", "resolved": "https://registry.npmjs.org/@radix-ui/react-menubar/-/react-menubar-1.1.16.tgz", "integrity": "sha512-EB1FktTz5xRRi2Er974AUQZWg2yVBb1yjip38/lgwtCVRd3a+maUoGHN/xs9Yv8SY8QwbSEb+YrxGadVWbEutA==", - "license": "MIT", "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-collection": "1.1.7", @@ -1665,7 +1818,6 @@ "version": "1.2.14", "resolved": "https://registry.npmjs.org/@radix-ui/react-navigation-menu/-/react-navigation-menu-1.2.14.tgz", "integrity": "sha512-YB9mTFQvCOAQMHU+C/jVl96WmuWeltyUEpRJJky51huhds5W2FQr1J8D/16sQlf0ozxkPK8uF3niQMdUwZPv5w==", - "license": "MIT", "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-collection": "1.1.7", @@ -1701,7 +1853,6 @@ "version": "0.1.8", "resolved": "https://registry.npmjs.org/@radix-ui/react-one-time-password-field/-/react-one-time-password-field-0.1.8.tgz", "integrity": "sha512-ycS4rbwURavDPVjCb5iS3aG4lURFDILi6sKI/WITUMZ13gMmn/xGjpLoqBAalhJaDk8I3UbCM5GzKHrnzwHbvg==", - "license": "MIT", "dependencies": { "@radix-ui/number": "1.1.1", "@radix-ui/primitive": "1.1.3", @@ -1735,7 +1886,6 @@ "version": "0.1.3", "resolved": "https://registry.npmjs.org/@radix-ui/react-password-toggle-field/-/react-password-toggle-field-0.1.3.tgz", "integrity": "sha512-/UuCrDBWravcaMix4TdT+qlNdVwOM1Nck9kWx/vafXsdfj1ChfhOdfi3cy9SGBpWgTXwYCuboT/oYpJy3clqfw==", - "license": "MIT", "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-compose-refs": "1.1.2", @@ -1765,7 +1915,6 @@ "version": "1.1.15", "resolved": "https://registry.npmjs.org/@radix-ui/react-popover/-/react-popover-1.1.15.tgz", "integrity": "sha512-kr0X2+6Yy/vJzLYJUPCZEc8SfQcf+1COFoAqauJm74umQhta9M7lNJHP7QQS3vkvcGLQUbWpMzwrXYwrYztHKA==", - "license": "MIT", "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-compose-refs": "1.1.2", @@ -1802,7 +1951,6 @@ "version": "1.2.8", "resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.2.8.tgz", "integrity": "sha512-0NJQ4LFFUuWkE7Oxf0htBKS6zLkkjBH+hM1uk7Ng705ReR8m/uelduy1DBo0PyBXPKVnBA6YBlU94MBGXrSBCw==", - "license": "MIT", "dependencies": { "@floating-ui/react-dom": "^2.0.0", "@radix-ui/react-arrow": "1.1.7", @@ -1834,7 +1982,6 @@ "version": "1.1.9", "resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.1.9.tgz", "integrity": "sha512-bpIxvq03if6UNwXZ+HTK71JLh4APvnXntDc6XOX8UVq4XQOVl7lwok0AvIl+b8zgCw3fSaVTZMpAPPagXbKmHQ==", - "license": "MIT", "dependencies": { "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-layout-effect": "1.1.1" @@ -1858,7 +2005,6 @@ "version": "1.1.5", "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.5.tgz", "integrity": "sha512-/jfEwNDdQVBCNvjkGit4h6pMOzq8bHkopq458dPt2lMjx+eBQUohZNG9A7DtO/O5ukSbxuaNGXMjHicgwy6rQQ==", - "license": "MIT", "dependencies": { "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-use-layout-effect": "1.1.1" @@ -1882,7 +2028,6 @@ "version": "2.1.3", "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.3.tgz", "integrity": "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==", - "license": "MIT", "dependencies": { "@radix-ui/react-slot": "1.2.3" }, @@ -1905,7 +2050,6 @@ "version": "1.1.7", "resolved": "https://registry.npmjs.org/@radix-ui/react-progress/-/react-progress-1.1.7.tgz", "integrity": "sha512-vPdg/tF6YC/ynuBIJlk1mm7Le0VgW6ub6J2UWnTQ7/D23KXcPI1qy+0vBkgKgd38RCMJavBXpB83HPNFMTb0Fg==", - "license": "MIT", "dependencies": { "@radix-ui/react-context": "1.1.2", "@radix-ui/react-primitive": "2.1.3" @@ -1929,7 +2073,6 @@ "version": "1.3.8", "resolved": "https://registry.npmjs.org/@radix-ui/react-radio-group/-/react-radio-group-1.3.8.tgz", "integrity": "sha512-VBKYIYImA5zsxACdisNQ3BjCBfmbGH3kQlnFVqlWU4tXwjy7cGX8ta80BcrO+WJXIn5iBylEH3K6ZTlee//lgQ==", - "license": "MIT", "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-compose-refs": "1.1.2", @@ -1961,7 +2104,6 @@ "version": "1.1.11", "resolved": "https://registry.npmjs.org/@radix-ui/react-roving-focus/-/react-roving-focus-1.1.11.tgz", "integrity": "sha512-7A6S9jSgm/S+7MdtNDSb+IU859vQqJ/QAtcYQcfFC6W8RS4IxIZDldLR0xqCFZ6DCyrQLjLPsxtTNch5jVA4lA==", - "license": "MIT", "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-collection": "1.1.7", @@ -1992,7 +2134,6 @@ "version": "1.2.10", "resolved": "https://registry.npmjs.org/@radix-ui/react-scroll-area/-/react-scroll-area-1.2.10.tgz", "integrity": "sha512-tAXIa1g3sM5CGpVT0uIbUx/U3Gs5N8T52IICuCtObaos1S8fzsrPXG5WObkQN3S6NVl6wKgPhAIiBGbWnvc97A==", - "license": "MIT", "dependencies": { "@radix-ui/number": "1.1.1", "@radix-ui/primitive": "1.1.3", @@ -2023,7 +2164,6 @@ "version": "2.2.6", "resolved": "https://registry.npmjs.org/@radix-ui/react-select/-/react-select-2.2.6.tgz", "integrity": "sha512-I30RydO+bnn2PQztvo25tswPH+wFBjehVGtmagkU78yMdwTwVf12wnAOF+AeP8S2N8xD+5UPbGhkUfPyvT+mwQ==", - "license": "MIT", "dependencies": { "@radix-ui/number": "1.1.1", "@radix-ui/primitive": "1.1.3", @@ -2066,7 +2206,6 @@ "version": "1.1.7", "resolved": "https://registry.npmjs.org/@radix-ui/react-separator/-/react-separator-1.1.7.tgz", "integrity": "sha512-0HEb8R9E8A+jZjvmFCy/J4xhbXy3TV+9XSnGJ3KvTtjlIUy/YQ/p6UYZvi7YbeoeXdyU9+Y3scizK6hkY37baA==", - "license": "MIT", "dependencies": { "@radix-ui/react-primitive": "2.1.3" }, @@ -2089,7 +2228,6 @@ "version": "1.3.6", "resolved": "https://registry.npmjs.org/@radix-ui/react-slider/-/react-slider-1.3.6.tgz", "integrity": "sha512-JPYb1GuM1bxfjMRlNLE+BcmBC8onfCi60Blk7OBqi2MLTFdS+8401U4uFjnwkOr49BLmXxLC6JHkvAsx5OJvHw==", - "license": "MIT", "dependencies": { "@radix-ui/number": "1.1.1", "@radix-ui/primitive": "1.1.3", @@ -2122,7 +2260,6 @@ "version": "1.2.3", "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", - "license": "MIT", "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, @@ -2140,7 +2277,6 @@ "version": "1.2.6", "resolved": "https://registry.npmjs.org/@radix-ui/react-switch/-/react-switch-1.2.6.tgz", "integrity": "sha512-bByzr1+ep1zk4VubeEVViV592vu2lHE2BZY5OnzehZqOOgogN80+mNtCqPkhn2gklJqOpxWgPoYTSnhBCqpOXQ==", - "license": "MIT", "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-compose-refs": "1.1.2", @@ -2169,7 +2305,6 @@ "version": "1.1.13", "resolved": "https://registry.npmjs.org/@radix-ui/react-tabs/-/react-tabs-1.1.13.tgz", "integrity": "sha512-7xdcatg7/U+7+Udyoj2zodtI9H/IIopqo+YOIcZOq1nJwXWBZ9p8xiu5llXlekDbZkca79a/fozEYQXIA4sW6A==", - "license": "MIT", "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-context": "1.1.2", @@ -2199,7 +2334,6 @@ "version": "1.2.15", "resolved": "https://registry.npmjs.org/@radix-ui/react-toast/-/react-toast-1.2.15.tgz", "integrity": "sha512-3OSz3TacUWy4WtOXV38DggwxoqJK4+eDkNMl5Z/MJZaoUPaP4/9lf81xXMe1I2ReTAptverZUpbPY4wWwWyL5g==", - "license": "MIT", "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-collection": "1.1.7", @@ -2233,7 +2367,6 @@ "version": "1.1.10", "resolved": "https://registry.npmjs.org/@radix-ui/react-toggle/-/react-toggle-1.1.10.tgz", "integrity": "sha512-lS1odchhFTeZv3xwHH31YPObmJn8gOg7Lq12inrr0+BH/l3Tsq32VfjqH1oh80ARM3mlkfMic15n0kg4sD1poQ==", - "license": "MIT", "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-primitive": "2.1.3", @@ -2258,7 +2391,6 @@ "version": "1.1.11", "resolved": "https://registry.npmjs.org/@radix-ui/react-toggle-group/-/react-toggle-group-1.1.11.tgz", "integrity": "sha512-5umnS0T8JQzQT6HbPyO7Hh9dgd82NmS36DQr+X/YJ9ctFNCiiQd6IJAYYZ33LUwm8M+taCz5t2ui29fHZc4Y6Q==", - "license": "MIT", "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-context": "1.1.2", @@ -2287,7 +2419,6 @@ "version": "1.1.11", "resolved": "https://registry.npmjs.org/@radix-ui/react-toolbar/-/react-toolbar-1.1.11.tgz", "integrity": "sha512-4ol06/1bLoFu1nwUqzdD4Y5RZ9oDdKeiHIsntug54Hcr1pgaHiPqHFEaXI1IFP/EsOfROQZ8Mig9VTIRza6Tjg==", - "license": "MIT", "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-context": "1.1.2", @@ -2316,7 +2447,6 @@ "version": "1.2.8", "resolved": "https://registry.npmjs.org/@radix-ui/react-tooltip/-/react-tooltip-1.2.8.tgz", "integrity": "sha512-tY7sVt1yL9ozIxvmbtN5qtmH2krXcBCfjEiCgKGLqunJHvgvZG2Pcl2oQ3kbcZARb1BGEHdkLzcYGO8ynVlieg==", - "license": "MIT", "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-compose-refs": "1.1.2", @@ -2350,7 +2480,6 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.1.tgz", "integrity": "sha512-FkBMwD+qbGQeMu1cOHnuGB6x4yzPjho8ap5WtbEJ26umhgqVXbhekKUQO+hZEL1vU92a3wHwdp0HAcqAUF5iDg==", - "license": "MIT", "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" @@ -2365,7 +2494,6 @@ "version": "1.2.2", "resolved": "https://registry.npmjs.org/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.2.2.tgz", "integrity": "sha512-BjasUjixPFdS+NKkypcyyN5Pmg83Olst0+c6vGov0diwTEo6mgdqVR6hxcEgFuh4QrAs7Rc+9KuGJ9TVCj0Zzg==", - "license": "MIT", "dependencies": { "@radix-ui/react-use-effect-event": "0.0.2", "@radix-ui/react-use-layout-effect": "1.1.1" @@ -2384,7 +2512,6 @@ "version": "0.0.2", "resolved": "https://registry.npmjs.org/@radix-ui/react-use-effect-event/-/react-use-effect-event-0.0.2.tgz", "integrity": "sha512-Qp8WbZOBe+blgpuUT+lw2xheLP8q0oatc9UpmiemEICxGvFLYmHm9QowVZGHtJlGbS6A6yJ3iViad/2cVjnOiA==", - "license": "MIT", "dependencies": { "@radix-ui/react-use-layout-effect": "1.1.1" }, @@ -2402,7 +2529,6 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/@radix-ui/react-use-escape-keydown/-/react-use-escape-keydown-1.1.1.tgz", "integrity": "sha512-Il0+boE7w/XebUHyBjroE+DbByORGR9KKmITzbR7MyQ4akpORYP/ZmbhAr0DG7RmmBqoOnZdy2QlvajJ2QA59g==", - "license": "MIT", "dependencies": { "@radix-ui/react-use-callback-ref": "1.1.1" }, @@ -2420,7 +2546,6 @@ "version": "0.1.0", "resolved": "https://registry.npmjs.org/@radix-ui/react-use-is-hydrated/-/react-use-is-hydrated-0.1.0.tgz", "integrity": "sha512-U+UORVEq+cTnRIaostJv9AGdV3G6Y+zbVd+12e18jQ5A3c0xL03IhnHuiU4UV69wolOQp5GfR58NW/EgdQhwOA==", - "license": "MIT", "dependencies": { "use-sync-external-store": "^1.5.0" }, @@ -2438,7 +2563,6 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.1.tgz", "integrity": "sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ==", - "license": "MIT", "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" @@ -2453,7 +2577,6 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/@radix-ui/react-use-previous/-/react-use-previous-1.1.1.tgz", "integrity": "sha512-2dHfToCj/pzca2Ck724OZ5L0EVrr3eHRNsG/b3xQJLA2hZpVCS99bLAX+hm1IHXDEnzU6by5z/5MIY794/a8NQ==", - "license": "MIT", "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" @@ -2468,7 +2591,6 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/@radix-ui/react-use-rect/-/react-use-rect-1.1.1.tgz", "integrity": "sha512-QTYuDesS0VtuHNNvMh+CjlKJ4LJickCMUAqjlE3+j8w+RlRpwyX3apEQKGFzbZGdo7XNG1tXa+bQqIE7HIXT2w==", - "license": "MIT", "dependencies": { "@radix-ui/rect": "1.1.1" }, @@ -2486,7 +2608,6 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/@radix-ui/react-use-size/-/react-use-size-1.1.1.tgz", "integrity": "sha512-ewrXRDTAqAXlkl6t/fkXWNAhFX9I+CkKlw6zjEwk86RSPKwZr3xpBRso655aqYafwtnbpHLj6toFzmd6xdVptQ==", - "license": "MIT", "dependencies": { "@radix-ui/react-use-layout-effect": "1.1.1" }, @@ -2504,7 +2625,6 @@ "version": "1.2.3", "resolved": "https://registry.npmjs.org/@radix-ui/react-visually-hidden/-/react-visually-hidden-1.2.3.tgz", "integrity": "sha512-pzJq12tEaaIhqjbzpCuv/OypJY/BPavOofm+dbab+MHLajy277+1lLm6JFcGgF5eskJ6mquGirhXY2GD/8u8Ug==", - "license": "MIT", "dependencies": { "@radix-ui/react-primitive": "2.1.3" }, @@ -2526,14 +2646,12 @@ "node_modules/@radix-ui/rect": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/@radix-ui/rect/-/rect-1.1.1.tgz", - "integrity": "sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw==", - "license": "MIT" + "integrity": "sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw==" }, "node_modules/@radix-ui/themes": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/@radix-ui/themes/-/themes-3.3.0.tgz", "integrity": "sha512-I0/h2CRNTpYNB7Mi3xFIvSsQq5a108d7kK8dTO5zp5b9HR5QJXKag6B8tjpz2ITkVYkFdkGk45doNkSr7OxwNw==", - "license": "MIT", "dependencies": { "@radix-ui/colors": "^3.0.0", "classnames": "^2.3.2", @@ -2559,150 +2677,142 @@ "version": "0.12.2", "resolved": "https://registry.npmjs.org/@react-oauth/google/-/google-0.12.2.tgz", "integrity": "sha512-d1GVm2uD4E44EJft2RbKtp8Z1fp/gK8Lb6KHgs3pHlM0PxCXGLaq8LLYQYENnN4xPWO1gkL4apBtlPKzpLvZwg==", - "license": "MIT", "peerDependencies": { "react": ">=16.8.0", "react-dom": ">=16.8.0" } }, - "node_modules/@remirror/core-constants": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@remirror/core-constants/-/core-constants-3.0.0.tgz", - "integrity": "sha512-42aWfPrimMfDKDi4YegyS7x+/0tlzaqwPQCULLanv3DMIlu96KTJR0fM5isWX2UViOqlGnX6YFgqWepcX+XMNg==", - "license": "MIT" - }, "node_modules/@rolldown/pluginutils": { - "version": "1.0.0-rc.2", - "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.2.tgz", - "integrity": "sha512-izyXV/v+cHiRfozX62W9htOAvwMo4/bXKDrQ+vom1L1qRuexPock/7VZDAhnpHCLNejd3NJ6hiab+tO0D44Rgw==", - "dev": true, - "license": "MIT" + "version": "1.0.0-beta.27", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.27.tgz", + "integrity": "sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==", + "dev": true }, "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.57.1.tgz", - "integrity": "sha512-A6ehUVSiSaaliTxai040ZpZ2zTevHYbvu/lDoeAteHI8QnaosIzm4qwtezfRg1jOYaUmnzLX1AOD6Z+UJjtifg==", + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.60.2.tgz", + "integrity": "sha512-dnlp69efPPg6Uaw2dVqzWRfAWRnYVb1XJ8CyyhIbZeaq4CA5/mLeZ1IEt9QqQxmbdvagjLIm2ZL8BxXv5lH4Yw==", "cpu": [ "arm" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "android" - ] + ], + "peer": true }, "node_modules/@rollup/rollup-android-arm64": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.57.1.tgz", - "integrity": "sha512-dQaAddCY9YgkFHZcFNS/606Exo8vcLHwArFZ7vxXq4rigo2bb494/xKMMwRRQW6ug7Js6yXmBZhSBRuBvCCQ3w==", + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.60.2.tgz", + "integrity": "sha512-OqZTwDRDchGRHHm/hwLOL7uVPB9aUvI0am/eQuWMNyFHf5PSEQmyEeYYheA0EPPKUO/l0uigCp+iaTjoLjVoHg==", "cpu": [ "arm64" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "android" - ] + ], + "peer": true }, "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.57.1.tgz", - "integrity": "sha512-crNPrwJOrRxagUYeMn/DZwqN88SDmwaJ8Cvi/TN1HnWBU7GwknckyosC2gd0IqYRsHDEnXf328o9/HC6OkPgOg==", + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.60.2.tgz", + "integrity": "sha512-UwRE7CGpvSVEQS8gUMBe1uADWjNnVgP3Iusyda1nSRwNDCsRjnGc7w6El6WLQsXmZTbLZx9cecegumcitNfpmA==", "cpu": [ "arm64" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "darwin" - ] + ], + "peer": true }, "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.57.1.tgz", - "integrity": "sha512-Ji8g8ChVbKrhFtig5QBV7iMaJrGtpHelkB3lsaKzadFBe58gmjfGXAOfI5FV0lYMH8wiqsxKQ1C9B0YTRXVy4w==", + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.60.2.tgz", + "integrity": "sha512-gjEtURKLCC5VXm1I+2i1u9OhxFsKAQJKTVB8WvDAHF+oZlq0GTVFOlTlO1q3AlCTE/DF32c16ESvfgqR7343/g==", "cpu": [ "x64" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "darwin" - ] + ], + "peer": true }, "node_modules/@rollup/rollup-freebsd-arm64": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.57.1.tgz", - "integrity": "sha512-R+/WwhsjmwodAcz65guCGFRkMb4gKWTcIeLy60JJQbXrJ97BOXHxnkPFrP+YwFlaS0m+uWJTstrUA9o+UchFug==", + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.60.2.tgz", + "integrity": "sha512-Bcl6CYDeAgE70cqZaMojOi/eK63h5Me97ZqAQoh77VPjMysA/4ORQBRGo3rRy45x4MzVlU9uZxs8Uwy7ZaKnBw==", "cpu": [ "arm64" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "freebsd" - ] + ], + "peer": true }, "node_modules/@rollup/rollup-freebsd-x64": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.57.1.tgz", - "integrity": "sha512-IEQTCHeiTOnAUC3IDQdzRAGj3jOAYNr9kBguI7MQAAZK3caezRrg0GxAb6Hchg4lxdZEI5Oq3iov/w/hnFWY9Q==", + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.60.2.tgz", + "integrity": "sha512-LU+TPda3mAE2QB0/Hp5VyeKJivpC6+tlOXd1VMoXV/YFMvk/MNk5iXeBfB4MQGRWyOYVJ01625vjkr0Az98OJQ==", "cpu": [ "x64" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "freebsd" - ] + ], + "peer": true }, "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.57.1.tgz", - "integrity": "sha512-F8sWbhZ7tyuEfsmOxwc2giKDQzN3+kuBLPwwZGyVkLlKGdV1nvnNwYD0fKQ8+XS6hp9nY7B+ZeK01EBUE7aHaw==", + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.60.2.tgz", + "integrity": "sha512-2QxQrM+KQ7DAW4o22j+XZ6RKdxjLD7BOWTP0Bv0tmjdyhXSsr2Ul1oJDQqh9Zf5qOwTuTc7Ek83mOFaKnodPjg==", "cpu": [ "arm" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "linux" - ] + ], + "peer": true }, "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.57.1.tgz", - "integrity": "sha512-rGfNUfn0GIeXtBP1wL5MnzSj98+PZe/AXaGBCRmT0ts80lU5CATYGxXukeTX39XBKsxzFpEeK+Mrp9faXOlmrw==", + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.60.2.tgz", + "integrity": "sha512-TbziEu2DVsTEOPif2mKWkMeDMLoYjx95oESa9fkQQK7r/Orta0gnkcDpzwufEcAO2BLBsD7mZkXGFqEdMRRwfw==", "cpu": [ "arm" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "linux" - ] + ], + "peer": true }, "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.57.1.tgz", - "integrity": "sha512-MMtej3YHWeg/0klK2Qodf3yrNzz6CGjo2UntLvk2RSPlhzgLvYEB3frRvbEF2wRKh1Z2fDIg9KRPe1fawv7C+g==", + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.60.2.tgz", + "integrity": "sha512-bO/rVDiDUuM2YfuCUwZ1t1cP+/yqjqz+Xf2VtkdppefuOFS2OSeAfgafaHNkFn0t02hEyXngZkxtGqXcXwO8Rg==", "cpu": [ "arm64" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "linux" - ] + ], + "peer": true }, "node_modules/@rollup/rollup-linux-arm64-musl": { "version": "4.57.1", @@ -2711,123 +2821,122 @@ "cpu": [ "arm64" ], - "license": "MIT", "optional": true, "os": [ "linux" ] }, "node_modules/@rollup/rollup-linux-loong64-gnu": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.57.1.tgz", - "integrity": "sha512-QWO6RQTZ/cqYtJMtxhkRkidoNGXc7ERPbZN7dVW5SdURuLeVU7lwKMpo18XdcmpWYd0qsP1bwKPf7DNSUinhvA==", + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.60.2.tgz", + "integrity": "sha512-pOjB/uSIyDt+ow3k/RcLvUAOGpysT2phDn7TTUB3n75SlIgZzM6NKAqlErPhoFU+npgY3/n+2HYIQVbF70P9/A==", "cpu": [ "loong64" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "linux" - ] + ], + "peer": true }, "node_modules/@rollup/rollup-linux-loong64-musl": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.57.1.tgz", - "integrity": "sha512-xpObYIf+8gprgWaPP32xiN5RVTi/s5FCR+XMXSKmhfoJjrpRAjCuuqQXyxUa/eJTdAE6eJ+KDKaoEqjZQxh3Gw==", + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.60.2.tgz", + "integrity": "sha512-2/w+q8jszv9Ww1c+6uJT3OwqhdmGP2/4T17cu8WuwyUuuaCDDJ2ojdyYwZzCxx0GcsZBhzi3HmH+J5pZNXnd+Q==", "cpu": [ "loong64" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "linux" - ] + ], + "peer": true }, "node_modules/@rollup/rollup-linux-ppc64-gnu": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.57.1.tgz", - "integrity": "sha512-4BrCgrpZo4hvzMDKRqEaW1zeecScDCR+2nZ86ATLhAoJ5FQ+lbHVD3ttKe74/c7tNT9c6F2viwB3ufwp01Oh2w==", + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.60.2.tgz", + "integrity": "sha512-11+aL5vKheYgczxtPVVRhdptAM2H7fcDR5Gw4/bTcteuZBlH4oP9f5s9zYO9aGZvoGeBpqXI/9TZZihZ609wKw==", "cpu": [ "ppc64" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "linux" - ] + ], + "peer": true }, "node_modules/@rollup/rollup-linux-ppc64-musl": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.57.1.tgz", - "integrity": "sha512-NOlUuzesGauESAyEYFSe3QTUguL+lvrN1HtwEEsU2rOwdUDeTMJdO5dUYl/2hKf9jWydJrO9OL/XSSf65R5+Xw==", + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.60.2.tgz", + "integrity": "sha512-i16fokAGK46IVZuV8LIIwMdtqhin9hfYkCh8pf8iC3QU3LpwL+1FSFGej+O7l3E/AoknL6Dclh2oTdnRMpTzFQ==", "cpu": [ "ppc64" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "linux" - ] + ], + "peer": true }, "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.57.1.tgz", - "integrity": "sha512-ptA88htVp0AwUUqhVghwDIKlvJMD/fmL/wrQj99PRHFRAG6Z5nbWoWG4o81Nt9FT+IuqUQi+L31ZKAFeJ5Is+A==", + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.60.2.tgz", + "integrity": "sha512-49FkKS6RGQoriDSK/6E2GkAsAuU5kETFCh7pG4yD/ylj9rKhTmO3elsnmBvRD4PgJPds5W2PkhC82aVwmUcJ7A==", "cpu": [ "riscv64" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "linux" - ] + ], + "peer": true }, "node_modules/@rollup/rollup-linux-riscv64-musl": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.57.1.tgz", - "integrity": "sha512-S51t7aMMTNdmAMPpBg7OOsTdn4tySRQvklmL3RpDRyknk87+Sp3xaumlatU+ppQ+5raY7sSTcC2beGgvhENfuw==", + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.60.2.tgz", + "integrity": "sha512-mjYNkHPfGpUR00DuM1ZZIgs64Hpf4bWcz9Z41+4Q+pgDx73UwWdAYyf6EG/lRFldmdHHzgrYyge5akFUW0D3mQ==", "cpu": [ "riscv64" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "linux" - ] + ], + "peer": true }, "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.57.1.tgz", - "integrity": "sha512-Bl00OFnVFkL82FHbEqy3k5CUCKH6OEJL54KCyx2oqsmZnFTR8IoNqBF+mjQVcRCT5sB6yOvK8A37LNm/kPJiZg==", + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.60.2.tgz", + "integrity": "sha512-ALyvJz965BQk8E9Al/JDKKDLH2kfKFLTGMlgkAbbYtZuJt9LU8DW3ZoDMCtQpXAltZxwBHevXz5u+gf0yA0YoA==", "cpu": [ "s390x" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "linux" - ] + ], + "peer": true }, "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.57.1.tgz", - "integrity": "sha512-ABca4ceT4N+Tv/GtotnWAeXZUZuM/9AQyCyKYyKnpk4yoA7QIAuBt6Hkgpw8kActYlew2mvckXkvx0FfoInnLg==", + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.60.2.tgz", + "integrity": "sha512-UQjrkIdWrKI626Du8lCQ6MJp/6V1LAo2bOK9OTu4mSn8GGXIkPXk/Vsp4bLHCd9Z9Iz2OTEaokUE90VweJgIYQ==", "cpu": [ "x64" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "linux" - ] + ], + "peer": true }, "node_modules/@rollup/rollup-linux-x64-musl": { "version": "4.57.1", @@ -2836,106 +2945,104 @@ "cpu": [ "x64" ], - "license": "MIT", "optional": true, "os": [ "linux" ] }, "node_modules/@rollup/rollup-openbsd-x64": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.57.1.tgz", - "integrity": "sha512-H+hXEv9gdVQuDTgnqD+SQffoWoc0Of59AStSzTEj/feWTBAnSfSD3+Dql1ZruJQxmykT/JVY0dE8Ka7z0DH1hw==", + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.60.2.tgz", + "integrity": "sha512-6d4Z3534xitaA1FcMWP7mQPq5zGwBmGbhphh2DwaA1aNIXUu3KTOfwrWpbwI4/Gr0uANo7NTtaykFyO2hPuFLg==", "cpu": [ "x64" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "openbsd" - ] + ], + "peer": true }, "node_modules/@rollup/rollup-openharmony-arm64": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.57.1.tgz", - "integrity": "sha512-4wYoDpNg6o/oPximyc/NG+mYUejZrCU2q+2w6YZqrAs2UcNUChIZXjtafAiiZSUc7On8v5NyNj34Kzj/Ltk6dQ==", + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.60.2.tgz", + "integrity": "sha512-NetAg5iO2uN7eB8zE5qrZ3CSil+7IJt4WDFLcC75Ymywq1VZVD6qJ6EvNLjZ3rEm6gB7XW5JdT60c6MN35Z85Q==", "cpu": [ "arm64" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "openharmony" - ] + ], + "peer": true }, "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.57.1.tgz", - "integrity": "sha512-O54mtsV/6LW3P8qdTcamQmuC990HDfR71lo44oZMZlXU4tzLrbvTii87Ni9opq60ds0YzuAlEr/GNwuNluZyMQ==", + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.60.2.tgz", + "integrity": "sha512-NCYhOotpgWZ5kdxCZsv6Iudx0wX8980Q/oW4pNFNihpBKsDbEA1zpkfxJGC0yugsUuyDZ7gL37dbzwhR0VI7pQ==", "cpu": [ "arm64" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "win32" - ] + ], + "peer": true }, "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.57.1.tgz", - "integrity": "sha512-P3dLS+IerxCT/7D2q2FYcRdWRl22dNbrbBEtxdWhXrfIMPP9lQhb5h4Du04mdl5Woq05jVCDPCMF7Ub0NAjIew==", + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.60.2.tgz", + "integrity": "sha512-RXsaOqXxfoUBQoOgvmmijVxJnW2IGB0eoMO7F8FAjaj0UTywUO/luSqimWBJn04WNgUkeNhh7fs7pESXajWmkg==", "cpu": [ "ia32" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "win32" - ] + ], + "peer": true }, "node_modules/@rollup/rollup-win32-x64-gnu": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.57.1.tgz", - "integrity": "sha512-VMBH2eOOaKGtIJYleXsi2B8CPVADrh+TyNxJ4mWPnKfLB/DBUmzW+5m1xUrcwWoMfSLagIRpjUFeW5CO5hyciQ==", + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.60.2.tgz", + "integrity": "sha512-qdAzEULD+/hzObedtmV6iBpdL5TIbKVztGiK7O3/KYSf+HIzU257+MX1EXJcyIiDbMAqmbwaufcYPvyRryeZtA==", "cpu": [ "x64" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "win32" - ] + ], + "peer": true }, "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.57.1.tgz", - "integrity": "sha512-mxRFDdHIWRxg3UfIIAwCm6NzvxG0jDX/wBN6KsQFTvKFqqg9vTrWUE68qEjHt19A5wwx5X5aUi2zuZT7YR0jrA==", + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.60.2.tgz", + "integrity": "sha512-Nd/SgG27WoA9e+/TdK74KnHz852TLa94ovOYySo/yMPuTmpckK/jIF2jSwS3g7ELSKXK13/cVdmg1Z/DaCWKxA==", "cpu": [ "x64" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "win32" - ] + ], + "peer": true }, "node_modules/@swc/core": { - "version": "1.15.11", - "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.15.11.tgz", - "integrity": "sha512-iLmLTodbYxU39HhMPaMUooPwO/zqJWvsqkrXv1ZI38rMb048p6N7qtAtTp37sw9NzSrvH6oli8EdDygo09IZ/w==", + "version": "1.15.32", + "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.15.32.tgz", + "integrity": "sha512-/eWL0n43D64QWEUHLtTE+jDqjkJhyidjkDhv6f0uJohOUAhywxQ9wXYp845DNNds0JpCdI4Uo0a9bl+vbXf+ew==", "dev": true, "hasInstallScript": true, - "license": "Apache-2.0", "dependencies": { "@swc/counter": "^0.1.3", - "@swc/types": "^0.1.25" + "@swc/types": "^0.1.26" }, "engines": { "node": ">=10" @@ -2945,16 +3052,18 @@ "url": "https://opencollective.com/swc" }, "optionalDependencies": { - "@swc/core-darwin-arm64": "1.15.11", - "@swc/core-darwin-x64": "1.15.11", - "@swc/core-linux-arm-gnueabihf": "1.15.11", - "@swc/core-linux-arm64-gnu": "1.15.11", - "@swc/core-linux-arm64-musl": "1.15.11", - "@swc/core-linux-x64-gnu": "1.15.11", - "@swc/core-linux-x64-musl": "1.15.11", - "@swc/core-win32-arm64-msvc": "1.15.11", - "@swc/core-win32-ia32-msvc": "1.15.11", - "@swc/core-win32-x64-msvc": "1.15.11" + "@swc/core-darwin-arm64": "1.15.32", + "@swc/core-darwin-x64": "1.15.32", + "@swc/core-linux-arm-gnueabihf": "1.15.32", + "@swc/core-linux-arm64-gnu": "1.15.32", + "@swc/core-linux-arm64-musl": "1.15.32", + "@swc/core-linux-ppc64-gnu": "1.15.32", + "@swc/core-linux-s390x-gnu": "1.15.32", + "@swc/core-linux-x64-gnu": "1.15.32", + "@swc/core-linux-x64-musl": "1.15.32", + "@swc/core-win32-arm64-msvc": "1.15.32", + "@swc/core-win32-ia32-msvc": "1.15.32", + "@swc/core-win32-x64-msvc": "1.15.32" }, "peerDependencies": { "@swc/helpers": ">=0.5.17" @@ -2966,14 +3075,13 @@ } }, "node_modules/@swc/core-darwin-arm64": { - "version": "1.15.11", - "resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.15.11.tgz", - "integrity": "sha512-QoIupRWVH8AF1TgxYyeA5nS18dtqMuxNwchjBIwJo3RdwLEFiJq6onOx9JAxHtuPwUkIVuU2Xbp+jCJ7Vzmgtg==", + "version": "1.15.32", + "resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.15.32.tgz", + "integrity": "sha512-/YWMvJDPu+AAwuUsM2G+DNQ/7zhodURGzdQyewEqcvgklAdDHs3LwQmLLnyn6SJl8DT8UOxkbzK+D1PmPeelRg==", "cpu": [ "arm64" ], "dev": true, - "license": "Apache-2.0 AND MIT", "optional": true, "os": [ "darwin" @@ -2983,14 +3091,13 @@ } }, "node_modules/@swc/core-darwin-x64": { - "version": "1.15.11", - "resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.15.11.tgz", - "integrity": "sha512-S52Gu1QtPSfBYDiejlcfp9GlN+NjTZBRRNsz8PNwBgSE626/FUf2PcllVUix7jqkoMC+t0rS8t+2/aSWlMuQtA==", + "version": "1.15.32", + "resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.15.32.tgz", + "integrity": "sha512-KOTXJXdAhWL+hZ77MYP3z+4pcMFaQhQ74yqyN1uz093q0YnbxpqMtYpPISbYvMHzVRNNx5kN+9RZAXEaadhWVA==", "cpu": [ "x64" ], "dev": true, - "license": "Apache-2.0 AND MIT", "optional": true, "os": [ "darwin" @@ -3000,14 +3107,13 @@ } }, "node_modules/@swc/core-linux-arm-gnueabihf": { - "version": "1.15.11", - "resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.15.11.tgz", - "integrity": "sha512-lXJs8oXo6Z4yCpimpQ8vPeCjkgoHu5NoMvmJZ8qxDyU99KVdg6KwU9H79vzrmB+HfH+dCZ7JGMqMF//f8Cfvdg==", + "version": "1.15.32", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.15.32.tgz", + "integrity": "sha512-oOoxLweljlc0A4X8ybsgxV7cVaYTwBOg2iMDJcFR3Sr48C+lsv9VzSmqdK/IVIXF4W4GjLc3VqTAdSMXlfVLuQ==", "cpu": [ "arm" ], "dev": true, - "license": "Apache-2.0", "optional": true, "os": [ "linux" @@ -3017,14 +3123,13 @@ } }, "node_modules/@swc/core-linux-arm64-gnu": { - "version": "1.15.11", - "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.15.11.tgz", - "integrity": "sha512-chRsz1K52/vj8Mfq/QOugVphlKPWlMh10V99qfH41hbGvwAU6xSPd681upO4bKiOr9+mRIZZW+EfJqY42ZzRyA==", + "version": "1.15.32", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.15.32.tgz", + "integrity": "sha512-oDzEkdl6D6BAWdMtU5KGO7y3HR5fJcvByNLyEk9+ugj8nP5Ovb7P4kBcStBXc4MPExFGQryehiINMlmY8HlclA==", "cpu": [ "arm64" ], "dev": true, - "license": "Apache-2.0 AND MIT", "optional": true, "os": [ "linux" @@ -3040,7 +3145,38 @@ "cpu": [ "arm64" ], - "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-ppc64-gnu": { + "version": "1.15.32", + "resolved": "https://registry.npmjs.org/@swc/core-linux-ppc64-gnu/-/core-linux-ppc64-gnu-1.15.32.tgz", + "integrity": "sha512-KGkTMyz/Tbn3PBNu0AVZ4GTDFKnICrYcTiNPZq8DrvK42pnFsf3GNDrIG9E5AtQlTmC0YigkWKmu0eMcfTrmgA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-s390x-gnu": { + "version": "1.15.32", + "resolved": "https://registry.npmjs.org/@swc/core-linux-s390x-gnu/-/core-linux-s390x-gnu-1.15.32.tgz", + "integrity": "sha512-G3Aa4tVS/3OGZBkoNIwUF9F6RAy+Osb4GOlo62SinLmDiErz/ykmM7KH0wkz6l9kM8jJq1HyAM6atJTUEbBk7g==", + "cpu": [ + "s390x" + ], + "dev": true, "optional": true, "os": [ "linux" @@ -3050,14 +3186,13 @@ } }, "node_modules/@swc/core-linux-x64-gnu": { - "version": "1.15.11", - "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.15.11.tgz", - "integrity": "sha512-DKtnJKIHiZdARyTKiX7zdRjiDS1KihkQWatQiCHMv+zc2sfwb4Glrodx2VLOX4rsa92NLR0Sw8WLcPEMFY1szQ==", + "version": "1.15.32", + "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.15.32.tgz", + "integrity": "sha512-ERsjfGcj6CBmj3vJnGDO8m8rTvw6RqMcWo1dogOtNx3/+/0+NNpJiXDobJrr1GwInI/BHAEkvSFIH6d2LqPcUQ==", "cpu": [ "x64" ], "dev": true, - "license": "Apache-2.0 AND MIT", "optional": true, "os": [ "linux" @@ -3073,7 +3208,6 @@ "cpu": [ "x64" ], - "license": "Apache-2.0 AND MIT", "optional": true, "os": [ "linux" @@ -3083,14 +3217,13 @@ } }, "node_modules/@swc/core-win32-arm64-msvc": { - "version": "1.15.11", - "resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.15.11.tgz", - "integrity": "sha512-ZkNNG5zL49YpaFzfl6fskNOSxtcZ5uOYmWBkY4wVAvgbSAQzLRVBp+xArGWh2oXlY/WgL99zQSGTv7RI5E6nzA==", + "version": "1.15.32", + "resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.15.32.tgz", + "integrity": "sha512-01yN0o9jvo8xBTP12aPK2wW8b41jmOlGbDDlAnoynotc4pO6xA0zby9f1z6j++qXDpGBttLySq1omgVrlQKYcw==", "cpu": [ "arm64" ], "dev": true, - "license": "Apache-2.0 AND MIT", "optional": true, "os": [ "win32" @@ -3100,14 +3233,13 @@ } }, "node_modules/@swc/core-win32-ia32-msvc": { - "version": "1.15.11", - "resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.15.11.tgz", - "integrity": "sha512-6XnzORkZCQzvTQ6cPrU7iaT9+i145oLwnin8JrfsLG41wl26+5cNQ2XV3zcbrnFEV6esjOceom9YO1w9mGJByw==", + "version": "1.15.32", + "resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.15.32.tgz", + "integrity": "sha512-fLagI9XZYNpTcmlqAcp3KBtmj7E19WCmYD80Jxj1Kn5tGNa7yxNLd3NNdWxuZGUPl5iC0/KqZru7g08gF6Fsrw==", "cpu": [ "ia32" ], "dev": true, - "license": "Apache-2.0 AND MIT", "optional": true, "os": [ "win32" @@ -3117,14 +3249,13 @@ } }, "node_modules/@swc/core-win32-x64-msvc": { - "version": "1.15.11", - "resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.15.11.tgz", - "integrity": "sha512-IQ2n6af7XKLL6P1gIeZACskSxK8jWtoKpJWLZmdXTDj1MGzktUy4i+FvpdtxFmJWNavRWH1VmTr6kAubRDHeKw==", + "version": "1.15.32", + "resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.15.32.tgz", + "integrity": "sha512-gbc2bQ/T2CiR+w0OvcVKwLOFAcPZBvmWmolbwpg1E8UrpeC03DGtyMUApOHNXNYWA3SHFrYXCQtosrcMza1YFg==", "cpu": [ "x64" ], "dev": true, - "license": "Apache-2.0 AND MIT", "optional": true, "os": [ "win32" @@ -3133,40 +3264,68 @@ "node": ">=10" } }, + "node_modules/@swc/core/node_modules/@swc/core-linux-arm64-musl": { + "version": "1.15.32", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.15.32.tgz", + "integrity": "sha512-omcqjoZP/b8D8PuczVoRwJieC6ibj7qIxTftNYokz4/aSmKFHvsd7nIFfPk5ZvtzncbH4AY7+Dkr/Lp2gWxYeA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core/node_modules/@swc/core-linux-x64-musl": { + "version": "1.15.32", + "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.15.32.tgz", + "integrity": "sha512-N4Ggahe/8SUbTX50P6EdhbW9YWcgbZVb52R4cq6MK+zsoMjRq7rGvV5ztA05QnbaCYqMYx8rTY7KAIA3Crdo4Q==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, "node_modules/@swc/counter": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz", "integrity": "sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==", - "dev": true, - "license": "Apache-2.0" + "dev": true }, "node_modules/@swc/types": { - "version": "0.1.25", - "resolved": "https://registry.npmjs.org/@swc/types/-/types-0.1.25.tgz", - "integrity": "sha512-iAoY/qRhNH8a/hBvm3zKj9qQ4oc2+3w1unPJa2XvTK3XjeLXtzcCingVPw/9e5mn1+0yPqxcBGp9Jf0pkfMb1g==", + "version": "0.1.26", + "resolved": "https://registry.npmjs.org/@swc/types/-/types-0.1.26.tgz", + "integrity": "sha512-lyMwd7WGgG79RS7EERZV3T8wMdmPq3xwyg+1nmAM64kIhx5yl+juO2PYIHb7vTiPgPCj8LYjsNV2T5wiQHUEaw==", "dev": true, - "license": "Apache-2.0", "dependencies": { "@swc/counter": "^0.1.3" } }, "node_modules/@tanstack/query-core": { - "version": "5.90.20", - "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.90.20.tgz", - "integrity": "sha512-OMD2HLpNouXEfZJWcKeVKUgQ5n+n3A2JFmBaScpNDUqSrQSjiveC7dKMe53uJUg1nDG16ttFPz2xfilz6i2uVg==", - "license": "MIT", + "version": "5.100.5", + "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.100.5.tgz", + "integrity": "sha512-t20KrhKkf0HXzqQkPbJ5erhFesup68BAbwFgYmTrS7bxMF7O5MdmL8jUkik4thsG7Hg00fblz30h6yF1d5TxGg==", "funding": { "type": "github", "url": "https://github.com/sponsors/tannerlinsley" } }, "node_modules/@tanstack/react-query": { - "version": "5.90.21", - "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.90.21.tgz", - "integrity": "sha512-0Lu6y5t+tvlTJMTO7oh5NSpJfpg/5D41LlThfepTixPYkJ0sE2Jj0m0f6yYqujBwIXlId87e234+MxG3D3g7kg==", - "license": "MIT", + "version": "5.100.5", + "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.100.5.tgz", + "integrity": "sha512-aNwj1mi2v2bQ9IxkyR1grLOUkv3BYWoykHy9KDyLNbjC3tsahbOHJibK+Wjtr1wRhG59/AvJhiJG5OlthaCgJA==", "dependencies": { - "@tanstack/query-core": "5.90.20" + "@tanstack/query-core": "5.100.5" }, "funding": { "type": "github", @@ -3181,7 +3340,6 @@ "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.4.1.tgz", "integrity": "sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==", "dev": true, - "license": "MIT", "peer": true, "dependencies": { "@babel/code-frame": "^7.10.4", @@ -3202,7 +3360,6 @@ "resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-6.9.1.tgz", "integrity": "sha512-zIcONa+hVtVSSep9UT3jZ5rizo2BsxgyDYU7WFD5eICBE7no3881HGeb/QkGfsJs6JTkY1aQhT7rIPC7e+0nnA==", "dev": true, - "license": "MIT", "dependencies": { "@adobe/css-tools": "^4.4.0", "aria-query": "^5.0.0", @@ -3221,15 +3378,13 @@ "version": "0.6.3", "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.6.3.tgz", "integrity": "sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/@testing-library/react": { "version": "16.3.2", "resolved": "https://registry.npmjs.org/@testing-library/react/-/react-16.3.2.tgz", "integrity": "sha512-XU5/SytQM+ykqMnAnvB2umaJNIOsLF3PVv//1Ew4CTcpz0/BRyy/af40qqrt7SjKpDdT1saBMc42CUok5gaw+g==", "dev": true, - "license": "MIT", "dependencies": { "@babel/runtime": "^7.12.5" }, @@ -3257,7 +3412,6 @@ "resolved": "https://registry.npmjs.org/@testing-library/user-event/-/user-event-14.6.1.tgz", "integrity": "sha512-vq7fv0rnt+QTXgPxr5Hjc210p6YKq2kmdziLgnsZGgLJ9e6VAShx1pACLuRjd/AS/sr7phAR58OIIpf0LlmQNw==", "dev": true, - "license": "MIT", "engines": { "node": ">=12", "npm": ">=6" @@ -3267,49 +3421,45 @@ } }, "node_modules/@tiptap/core": { - "version": "3.20.1", - "resolved": "https://registry.npmjs.org/@tiptap/core/-/core-3.20.1.tgz", - "integrity": "sha512-SwkPEWIfaDEZjC8SEIi4kZjqIYUbRgLUHUuQezo5GbphUNC8kM1pi3C3EtoOPtxXrEbY6e4pWEzW54Pcrd+rVA==", - "license": "MIT", + "version": "3.22.4", + "resolved": "https://registry.npmjs.org/@tiptap/core/-/core-3.22.4.tgz", + "integrity": "sha512-vGIGm/HpqLg8EAAQXQ+koV+/S828OEpzocfWcPOwo1u2QUVf9dQG47Yy6JJ8zFFaJwfv4dBcOXli+7BrJwsxDQ==", "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" }, "peerDependencies": { - "@tiptap/pm": "^3.20.1" + "@tiptap/pm": "3.22.4" } }, "node_modules/@tiptap/extension-blockquote": { - "version": "3.19.0", - "resolved": "https://registry.npmjs.org/@tiptap/extension-blockquote/-/extension-blockquote-3.19.0.tgz", - "integrity": "sha512-y3UfqY9KD5XwWz3ndiiJ089Ij2QKeiXy/g1/tlAN/F1AaWsnkHEHMLxCP1BIqmMpwsX7rZjMLN7G5Lp7c9682A==", - "license": "MIT", + "version": "3.22.4", + "resolved": "https://registry.npmjs.org/@tiptap/extension-blockquote/-/extension-blockquote-3.22.4.tgz", + "integrity": "sha512-7/61kNPbGFhMgM//zMknD0pSb69rGdRIkpulXOWS1JBrFHkH6hjZDfrOETNzgKkO+NlmzVl9rXSTv0xauS3lzA==", "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" }, "peerDependencies": { - "@tiptap/core": "^3.19.0" + "@tiptap/core": "3.22.4" } }, "node_modules/@tiptap/extension-bold": { - "version": "3.19.0", - "resolved": "https://registry.npmjs.org/@tiptap/extension-bold/-/extension-bold-3.19.0.tgz", - "integrity": "sha512-UZgb1d0XK4J/JRIZ7jW+s4S6KjuEDT2z1PPM6ugcgofgJkWQvRZelCPbmtSFd3kwsD+zr9UPVgTh9YIuGQ8t+Q==", - "license": "MIT", + "version": "3.22.4", + "resolved": "https://registry.npmjs.org/@tiptap/extension-bold/-/extension-bold-3.22.4.tgz", + "integrity": "sha512-jIaPKfNOQu2lhpbLDvtwlQqM+mjF+Kk+auHpzYjBnsuwUli1Cl5ZOau7RH+rru/SQvZe1DtpQlANujDywugZAA==", "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" }, "peerDependencies": { - "@tiptap/core": "^3.19.0" + "@tiptap/core": "3.22.4" } }, "node_modules/@tiptap/extension-bubble-menu": { - "version": "3.19.0", - "resolved": "https://registry.npmjs.org/@tiptap/extension-bubble-menu/-/extension-bubble-menu-3.19.0.tgz", - "integrity": "sha512-klNVIYGCdznhFkrRokzGd6cwzoi8J7E5KbuOfZBwFwhMKZhlz/gJfKmYg9TJopeUhrr2Z9yHgWTk8dh/YIJCdQ==", - "license": "MIT", + "version": "3.22.4", + "resolved": "https://registry.npmjs.org/@tiptap/extension-bubble-menu/-/extension-bubble-menu-3.22.4.tgz", + "integrity": "sha512-v4pux5Ql3THAEjaLMY4ldtdy/Xy2qU7PJLBkq8ugLp8qicaKC+tpqxp6sGif4vLIjz7Ap5hurRbTNbXzszyyHA==", "optional": true, "dependencies": { "@floating-ui/dom": "^1.0.0" @@ -3319,81 +3469,75 @@ "url": "https://github.com/sponsors/ueberdosis" }, "peerDependencies": { - "@tiptap/core": "^3.19.0", - "@tiptap/pm": "^3.19.0" + "@tiptap/core": "3.22.4", + "@tiptap/pm": "3.22.4" } }, "node_modules/@tiptap/extension-bullet-list": { - "version": "3.19.0", - "resolved": "https://registry.npmjs.org/@tiptap/extension-bullet-list/-/extension-bullet-list-3.19.0.tgz", - "integrity": "sha512-F9uNnqd0xkJbMmRxVI5RuVxwB9JaCH/xtRqOUNQZnRBt7IdAElCY+Dvb4hMCtiNv+enGM/RFGJuFHR9TxmI7rw==", - "license": "MIT", + "version": "3.22.4", + "resolved": "https://registry.npmjs.org/@tiptap/extension-bullet-list/-/extension-bullet-list-3.22.4.tgz", + "integrity": "sha512-TB+d3fGcTixYjO7coKqTr1mGTJuqr8hjDCPUFgzuvKyJnBhqWITmBzQ/8CLq4rr6mihgGURbD3N+xkQuPAKFiw==", "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" }, "peerDependencies": { - "@tiptap/extension-list": "^3.19.0" + "@tiptap/extension-list": "3.22.4" } }, "node_modules/@tiptap/extension-code": { - "version": "3.19.0", - "resolved": "https://registry.npmjs.org/@tiptap/extension-code/-/extension-code-3.19.0.tgz", - "integrity": "sha512-2kqqQIXBXj2Or+4qeY3WoE7msK+XaHKL6EKOcKlOP2BW8eYqNTPzNSL+PfBDQ3snA7ljZQkTs/j4GYDj90vR1A==", - "license": "MIT", + "version": "3.22.4", + "resolved": "https://registry.npmjs.org/@tiptap/extension-code/-/extension-code-3.22.4.tgz", + "integrity": "sha512-cnbxmVhAcc7X3G81QUYEmKP0ve2hRmvAiFXBuuv9RUtQlBiRnzmhHoJOMgkX0CsMR7+8kMRpTfeDUYq2xp5s5w==", "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" }, "peerDependencies": { - "@tiptap/core": "^3.19.0" + "@tiptap/core": "3.22.4" } }, "node_modules/@tiptap/extension-code-block": { - "version": "3.19.0", - "resolved": "https://registry.npmjs.org/@tiptap/extension-code-block/-/extension-code-block-3.19.0.tgz", - "integrity": "sha512-b/2qR+tMn8MQb+eaFYgVk4qXnLNkkRYmwELQ8LEtEDQPxa5Vl7J3eu8+4OyoIFhZrNDZvvoEp80kHMCP8sI6rg==", - "license": "MIT", + "version": "3.22.4", + "resolved": "https://registry.npmjs.org/@tiptap/extension-code-block/-/extension-code-block-3.22.4.tgz", + "integrity": "sha512-MEurzNXfMET3rhjpoPJYUgMfxTdTqbzT9+ToFrqNGAHocdXVm6m1hhO2frVC7fEtHPnxXKsn0Z3NUbCRkRTLuA==", "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" }, "peerDependencies": { - "@tiptap/core": "^3.19.0", - "@tiptap/pm": "^3.19.0" + "@tiptap/core": "3.22.4", + "@tiptap/pm": "3.22.4" } }, "node_modules/@tiptap/extension-document": { - "version": "3.19.0", - "resolved": "https://registry.npmjs.org/@tiptap/extension-document/-/extension-document-3.19.0.tgz", - "integrity": "sha512-AOf0kHKSFO0ymjVgYSYDncRXTITdTcrj1tqxVazrmO60KNl1Rc2dAggDvIVTEBy5NvceF0scc7q3sE/5ZtVV7A==", - "license": "MIT", + "version": "3.22.4", + "resolved": "https://registry.npmjs.org/@tiptap/extension-document/-/extension-document-3.22.4.tgz", + "integrity": "sha512-XQKla1+703FqQJC48tPDVgt9ucGiFbIEmQdOg5L5o07z9a6/NzuaZAc+1zJ7NxcUZzy+z6wBn1PrVMTiqiSXlw==", "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" }, "peerDependencies": { - "@tiptap/core": "^3.19.0" + "@tiptap/core": "3.22.4" } }, "node_modules/@tiptap/extension-dropcursor": { - "version": "3.19.0", - "resolved": "https://registry.npmjs.org/@tiptap/extension-dropcursor/-/extension-dropcursor-3.19.0.tgz", - "integrity": "sha512-sf3dEZXiLvsGqVK2maUIzXY6qtYYCvBumag7+VPTMGQ0D4hiZ1X/4ukt4+6VXDg5R2WP1CoIt/QvUetUjWNhbQ==", - "license": "MIT", + "version": "3.22.4", + "resolved": "https://registry.npmjs.org/@tiptap/extension-dropcursor/-/extension-dropcursor-3.22.4.tgz", + "integrity": "sha512-N9/yMDC35jJp0V/naL0+6gi4gUDUIcPpWEzFdCDWUSYBA8mt41c1kI1ZU7UTKYIBzTClenhYHRc2XKZxxx0+LQ==", "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" }, "peerDependencies": { - "@tiptap/extensions": "^3.19.0" + "@tiptap/extensions": "3.22.4" } }, "node_modules/@tiptap/extension-floating-menu": { - "version": "3.19.0", - "resolved": "https://registry.npmjs.org/@tiptap/extension-floating-menu/-/extension-floating-menu-3.19.0.tgz", - "integrity": "sha512-JaoEkVRkt+Slq3tySlIsxnMnCjS0L5n1CA1hctjLy0iah8edetj3XD5mVv5iKqDzE+LIjF4nwLRRVKJPc8hFBg==", - "license": "MIT", + "version": "3.22.4", + "resolved": "https://registry.npmjs.org/@tiptap/extension-floating-menu/-/extension-floating-menu-3.22.4.tgz", + "integrity": "sha512-DFuyYxgaZPgxum5z1yvJPbfYCvDdO8geXsdyqt0qYYdiat3aGE4ncJhiLRIFDhSHBhaZg5eCgu/YPYAN6jZnrA==", "optional": true, "funding": { "type": "github", @@ -3401,81 +3545,75 @@ }, "peerDependencies": { "@floating-ui/dom": "^1.0.0", - "@tiptap/core": "^3.19.0", - "@tiptap/pm": "^3.19.0" + "@tiptap/core": "3.22.4", + "@tiptap/pm": "3.22.4" } }, "node_modules/@tiptap/extension-gapcursor": { - "version": "3.19.0", - "resolved": "https://registry.npmjs.org/@tiptap/extension-gapcursor/-/extension-gapcursor-3.19.0.tgz", - "integrity": "sha512-w7DACS4oSZaDWjz7gropZHPc9oXqC9yERZTcjWxyORuuIh1JFf0TRYspleK+OK28plK/IftojD/yUDn1MTRhvA==", - "license": "MIT", + "version": "3.22.4", + "resolved": "https://registry.npmjs.org/@tiptap/extension-gapcursor/-/extension-gapcursor-3.22.4.tgz", + "integrity": "sha512-UYBEUj3SFpKINIE7AdzcyeS3xICK+ee+YLBbuqNXyHStYChjJOohzJehqiqhjR16A88KQQ+ZjgyDcItKGygSog==", "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" }, "peerDependencies": { - "@tiptap/extensions": "^3.19.0" + "@tiptap/extensions": "3.22.4" } }, "node_modules/@tiptap/extension-hard-break": { - "version": "3.19.0", - "resolved": "https://registry.npmjs.org/@tiptap/extension-hard-break/-/extension-hard-break-3.19.0.tgz", - "integrity": "sha512-lAmQraYhPS5hafvCl74xDB5+bLuNwBKIEsVoim35I0sDJj5nTrfhaZgMJ91VamMvT+6FF5f1dvBlxBxAWa8jew==", - "license": "MIT", + "version": "3.22.4", + "resolved": "https://registry.npmjs.org/@tiptap/extension-hard-break/-/extension-hard-break-3.22.4.tgz", + "integrity": "sha512-xq+a4dE7T6VwApCkh/yU3p30gn3F8g8Arb9CyEZm58/WIJUIGvHSTjDdHmvU16+kiWSBg+wOOsaFHhYjJjxcKA==", "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" }, "peerDependencies": { - "@tiptap/core": "^3.19.0" + "@tiptap/core": "3.22.4" } }, "node_modules/@tiptap/extension-heading": { - "version": "3.19.0", - "resolved": "https://registry.npmjs.org/@tiptap/extension-heading/-/extension-heading-3.19.0.tgz", - "integrity": "sha512-uLpLlfyp086WYNOc0ekm1gIZNlEDfmzOhKzB0Hbyi6jDagTS+p9mxUNYeYOn9jPUxpFov43+Wm/4E24oY6B+TQ==", - "license": "MIT", + "version": "3.22.4", + "resolved": "https://registry.npmjs.org/@tiptap/extension-heading/-/extension-heading-3.22.4.tgz", + "integrity": "sha512-TUaj5f0Ir5qy9HKKt2ocnwfXKpZDYeHgbbP9gshKFzdq5PLe1RbIgkjfy6bnoI865cYjmPYWRjcT7XsKyIcb9Q==", "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" }, "peerDependencies": { - "@tiptap/core": "^3.19.0" + "@tiptap/core": "3.22.4" } }, "node_modules/@tiptap/extension-horizontal-rule": { - "version": "3.19.0", - "resolved": "https://registry.npmjs.org/@tiptap/extension-horizontal-rule/-/extension-horizontal-rule-3.19.0.tgz", - "integrity": "sha512-iqUHmgMGhMgYGwG6L/4JdelVQ5Mstb4qHcgTGd/4dkcUOepILvhdxajPle7OEdf9sRgjQO6uoAU5BVZVC26+ng==", - "license": "MIT", + "version": "3.22.4", + "resolved": "https://registry.npmjs.org/@tiptap/extension-horizontal-rule/-/extension-horizontal-rule-3.22.4.tgz", + "integrity": "sha512-cCI1HekGQwhY/MbgaKQ0R/7HcH5ZM1oFAyI/J72QGLC0XnF403S/OXoHMuBWr1mCu8hNiQWCzeNRJUty0iytNw==", "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" }, "peerDependencies": { - "@tiptap/core": "^3.19.0", - "@tiptap/pm": "^3.19.0" + "@tiptap/core": "3.22.4", + "@tiptap/pm": "3.22.4" } }, "node_modules/@tiptap/extension-italic": { - "version": "3.19.0", - "resolved": "https://registry.npmjs.org/@tiptap/extension-italic/-/extension-italic-3.19.0.tgz", - "integrity": "sha512-6GffxOnS/tWyCbDkirWNZITiXRta9wrCmrfa4rh+v32wfaOL1RRQNyqo9qN6Wjyl1R42Js+yXTzTTzZsOaLMYA==", - "license": "MIT", + "version": "3.22.4", + "resolved": "https://registry.npmjs.org/@tiptap/extension-italic/-/extension-italic-3.22.4.tgz", + "integrity": "sha512-fVSDx5AYXgDI3v2zZIqb7V8EewthwM2NJ/ZCX+XaxRsqNEpnjVhgHs7UlvDqK1wj2OJ6zmUNjPtVlAFRxwT+HQ==", "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" }, "peerDependencies": { - "@tiptap/core": "^3.19.0" + "@tiptap/core": "3.22.4" } }, "node_modules/@tiptap/extension-link": { - "version": "3.19.0", - "resolved": "https://registry.npmjs.org/@tiptap/extension-link/-/extension-link-3.19.0.tgz", - "integrity": "sha512-HEGDJnnCPfr7KWu7Dsq+eRRe/mBCsv6DuI+7fhOCLDJjjKzNgrX2abbo/zG3D/4lCVFaVb+qawgJubgqXR/Smw==", - "license": "MIT", + "version": "3.22.4", + "resolved": "https://registry.npmjs.org/@tiptap/extension-link/-/extension-link-3.22.4.tgz", + "integrity": "sha512-uoP3yus02uwGPVzW2QaEPJWVIrUb/r5nKm6c8DiJv9fNSX1+gykZZMg42c6GwRFLZ/vyfWjVCbAE03VMUqafgA==", "dependencies": { "linkifyjs": "^4.3.2" }, @@ -3484,179 +3622,161 @@ "url": "https://github.com/sponsors/ueberdosis" }, "peerDependencies": { - "@tiptap/core": "^3.19.0", - "@tiptap/pm": "^3.19.0" + "@tiptap/core": "3.22.4", + "@tiptap/pm": "3.22.4" } }, "node_modules/@tiptap/extension-list": { - "version": "3.19.0", - "resolved": "https://registry.npmjs.org/@tiptap/extension-list/-/extension-list-3.19.0.tgz", - "integrity": "sha512-N6nKbFB2VwMsPlCw67RlAtYSK48TAsAUgjnD+vd3ieSlIufdQnLXDFUP6hFKx9mwoUVUgZGz02RA6bkxOdYyTw==", - "license": "MIT", + "version": "3.22.4", + "resolved": "https://registry.npmjs.org/@tiptap/extension-list/-/extension-list-3.22.4.tgz", + "integrity": "sha512-Xe8UFvvHmyp/c/TJsFwlwU9CWACYbBirNsluJ3U1+H8BTu1wqdrT/AXR5uIXeyCl5kiWKgX5q71eHWbYFOrqrg==", "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" }, "peerDependencies": { - "@tiptap/core": "^3.19.0", - "@tiptap/pm": "^3.19.0" + "@tiptap/core": "3.22.4", + "@tiptap/pm": "3.22.4" } }, "node_modules/@tiptap/extension-list-item": { - "version": "3.19.0", - "resolved": "https://registry.npmjs.org/@tiptap/extension-list-item/-/extension-list-item-3.19.0.tgz", - "integrity": "sha512-VsSKuJz4/Tb6ZmFkXqWpDYkRzmaLTyE6dNSEpNmUpmZ32sMqo58mt11/huADNwfBFB0Ve7siH/VnFNIJYY3xvg==", - "license": "MIT", + "version": "3.22.4", + "resolved": "https://registry.npmjs.org/@tiptap/extension-list-item/-/extension-list-item-3.22.4.tgz", + "integrity": "sha512-H659KXTvggSypIDWSOJBZ37jh9pKjQriDDvYPYvOZCdfij0D0hsDXN/wXoypArneUkoBdgruHfTtMkFOaQlgkw==", "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" }, "peerDependencies": { - "@tiptap/extension-list": "^3.19.0" + "@tiptap/extension-list": "3.22.4" } }, "node_modules/@tiptap/extension-list-keymap": { - "version": "3.19.0", - "resolved": "https://registry.npmjs.org/@tiptap/extension-list-keymap/-/extension-list-keymap-3.19.0.tgz", - "integrity": "sha512-bxgmAgA3RzBGA0GyTwS2CC1c+QjkJJq9hC+S6PSOWELGRiTbwDN3MANksFXLjntkTa0N5fOnL27vBHtMStURqw==", - "license": "MIT", + "version": "3.22.4", + "resolved": "https://registry.npmjs.org/@tiptap/extension-list-keymap/-/extension-list-keymap-3.22.4.tgz", + "integrity": "sha512-t/zhker4oIS78AIGYDdFFfZC6zSBlszfD7z/zqFLGCg5PHNNgkZK5hKj6Vyix6D2SapRn/ajnx+8mhbKIUH5eA==", "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" }, "peerDependencies": { - "@tiptap/extension-list": "^3.19.0" + "@tiptap/extension-list": "3.22.4" } }, "node_modules/@tiptap/extension-mention": { - "version": "3.20.1", - "resolved": "https://registry.npmjs.org/@tiptap/extension-mention/-/extension-mention-3.20.1.tgz", - "integrity": "sha512-KOGokj7oH1QpcM8P02V+o6wHsVE0g7XEtdIy2vtq2vlFE3npNNNFkMa8F8VWX6qyC+VeVrNU6SIzS5MFY2TORA==", - "license": "MIT", + "version": "3.22.4", + "resolved": "https://registry.npmjs.org/@tiptap/extension-mention/-/extension-mention-3.22.4.tgz", + "integrity": "sha512-ZUJ1gCZlH+JGTAT7lVpZjcMAzvIi9hXIyBjOmjL+737NlF+Cfgo+fjHqFQgOSsiO9LEyc3ZMSclmbzII1Jy6IQ==", "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" }, "peerDependencies": { - "@tiptap/core": "^3.20.1", - "@tiptap/pm": "^3.20.1", - "@tiptap/suggestion": "^3.20.1" + "@tiptap/core": "3.22.4", + "@tiptap/pm": "3.22.4", + "@tiptap/suggestion": "3.22.4" } }, "node_modules/@tiptap/extension-ordered-list": { - "version": "3.19.0", - "resolved": "https://registry.npmjs.org/@tiptap/extension-ordered-list/-/extension-ordered-list-3.19.0.tgz", - "integrity": "sha512-cxGsINquwHYE1kmhAcLNLHAofmoDEG6jbesR5ybl7tU5JwtKVO7S/xZatll2DU1dsDAXWPWEeeMl4e/9svYjCg==", - "license": "MIT", + "version": "3.22.4", + "resolved": "https://registry.npmjs.org/@tiptap/extension-ordered-list/-/extension-ordered-list-3.22.4.tgz", + "integrity": "sha512-w77hPVf7pcHt97vfrybg/l0t5CimCd4y75OJKuHuo3CfgM5xbUP/gaPNMDyLLe7MYole/UHi/XvG3XjgzqTzAw==", "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" }, "peerDependencies": { - "@tiptap/extension-list": "^3.19.0" + "@tiptap/extension-list": "3.22.4" } }, "node_modules/@tiptap/extension-paragraph": { - "version": "3.19.0", - "resolved": "https://registry.npmjs.org/@tiptap/extension-paragraph/-/extension-paragraph-3.19.0.tgz", - "integrity": "sha512-xWa6gj82l5+AzdYyrSk9P4ynySaDzg/SlR1FarXE5yPXibYzpS95IWaVR0m2Qaz7Rrk+IiYOTGxGRxcHLOelNg==", - "license": "MIT", + "version": "3.22.4", + "resolved": "https://registry.npmjs.org/@tiptap/extension-paragraph/-/extension-paragraph-3.22.4.tgz", + "integrity": "sha512-de6dFkIhigiENESY6rNJ3yTVS/337ybfP30dNPudTwGe9oAu9ZCS+04j6QCvXSjhlI3ULiv7wiSHqrP26Gd+Hw==", "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" }, "peerDependencies": { - "@tiptap/core": "^3.19.0" + "@tiptap/core": "3.22.4" } }, "node_modules/@tiptap/extension-placeholder": { - "version": "3.19.0", - "resolved": "https://registry.npmjs.org/@tiptap/extension-placeholder/-/extension-placeholder-3.19.0.tgz", - "integrity": "sha512-i15OfgyI4IDCYAcYSKUMnuZkYuUInfanjf9zquH8J2BETiomf/jZldVCp/QycMJ8DOXZ38fXDc99wOygnSNySg==", - "license": "MIT", + "version": "3.22.4", + "resolved": "https://registry.npmjs.org/@tiptap/extension-placeholder/-/extension-placeholder-3.22.4.tgz", + "integrity": "sha512-Z3wtWL+KufwkC7CkJge5enAxx4q8C3oOYixme02snY9zfjX3V/1pjAmEfP4wxScgM5GIuTEJ83B9Yz3wRzPA6Q==", "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" }, "peerDependencies": { - "@tiptap/extensions": "^3.19.0" + "@tiptap/extensions": "3.22.4" } }, "node_modules/@tiptap/extension-strike": { - "version": "3.19.0", - "resolved": "https://registry.npmjs.org/@tiptap/extension-strike/-/extension-strike-3.19.0.tgz", - "integrity": "sha512-xYpabHsv7PccLUBQaP8AYiFCnYbx6P93RHPd0lgNwhdOjYFd931Zy38RyoxPHAgbYVmhf1iyx7lpuLtBnhS5dA==", - "license": "MIT", + "version": "3.22.4", + "resolved": "https://registry.npmjs.org/@tiptap/extension-strike/-/extension-strike-3.22.4.tgz", + "integrity": "sha512-aRHWQj42HiailXSC9LkKYM3jWMcSeGwOjbqM4PiuxQZmHVDRFmeHkfJItOdn2cSHaO0vuEVK+TvrWUWsBFi3pg==", "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" }, "peerDependencies": { - "@tiptap/core": "^3.19.0" + "@tiptap/core": "3.22.4" } }, "node_modules/@tiptap/extension-text": { - "version": "3.19.0", - "resolved": "https://registry.npmjs.org/@tiptap/extension-text/-/extension-text-3.19.0.tgz", - "integrity": "sha512-K95+SnbZy0h6hNFtfy23n8t/nOcTFEf69In9TSFVVmwn/Nwlke+IfiESAkqbt1/7sKJeegRXYO7WzFEmFl9Q/g==", - "license": "MIT", + "version": "3.22.4", + "resolved": "https://registry.npmjs.org/@tiptap/extension-text/-/extension-text-3.22.4.tgz", + "integrity": "sha512-mM69uUW5cSxIhyEpWXi/YcfyupcJMDLCPEfYi62awH0iOP/LRoCv/nHjJq4Hyj/KxRJbe8HKwIUnqaCUf7m5Pg==", "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" }, "peerDependencies": { - "@tiptap/core": "^3.19.0" + "@tiptap/core": "3.22.4" } }, "node_modules/@tiptap/extension-underline": { - "version": "3.19.0", - "resolved": "https://registry.npmjs.org/@tiptap/extension-underline/-/extension-underline-3.19.0.tgz", - "integrity": "sha512-800MGEWfG49j10wQzAFiW/ele1HT04MamcL8iyuPNu7ZbjbGN2yknvdrJlRy7hZlzIrVkZMr/1tz62KN33VHIw==", - "license": "MIT", + "version": "3.22.4", + "resolved": "https://registry.npmjs.org/@tiptap/extension-underline/-/extension-underline-3.22.4.tgz", + "integrity": "sha512-08kGdbhIrA6h10GWXqOkqIveaBj5tmxclK208/nUIAlonI9hPd739vu7fmVtpnmqCnSSNpoRtU4u6Gj5at0ZpA==", "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" }, "peerDependencies": { - "@tiptap/core": "^3.19.0" + "@tiptap/core": "3.22.4" } }, "node_modules/@tiptap/extensions": { - "version": "3.19.0", - "resolved": "https://registry.npmjs.org/@tiptap/extensions/-/extensions-3.19.0.tgz", - "integrity": "sha512-ZmGUhLbMWaGqnJh2Bry+6V4M6gMpUDYo4D1xNux5Gng/E/eYtc+PMxMZ/6F7tNTAuujLBOQKj6D+4SsSm457jw==", - "license": "MIT", + "version": "3.22.4", + "resolved": "https://registry.npmjs.org/@tiptap/extensions/-/extensions-3.22.4.tgz", + "integrity": "sha512-fOe8VptJvLPs32bNdUYo8SRyljwqKNQVXWW056VoXIc5en/59OdJlJQVeHI0jRRciH3MtrqODi/gfJR0VHNZ8A==", "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" }, "peerDependencies": { - "@tiptap/core": "^3.19.0", - "@tiptap/pm": "^3.19.0" + "@tiptap/core": "3.22.4", + "@tiptap/pm": "3.22.4" } }, "node_modules/@tiptap/pm": { - "version": "3.20.1", - "resolved": "https://registry.npmjs.org/@tiptap/pm/-/pm-3.20.1.tgz", - "integrity": "sha512-6kCiGLvpES4AxcEuOhb7HR7/xIeJWMjZlb6J7e8zpiIh5BoQc7NoRdctsnmFEjZvC19bIasccshHQ7H2zchWqw==", - "license": "MIT", + "version": "3.22.4", + "resolved": "https://registry.npmjs.org/@tiptap/pm/-/pm-3.22.4.tgz", + "integrity": "sha512-hj8Qka6WcHRllHUdeSjDnq2XaisUo4KsoGJc1WcFpoa1Yd+OeD861zUMnV7DFVGdZRy45Obht0CUYJpXQ4yA4w==", "dependencies": { "prosemirror-changeset": "^2.3.0", - "prosemirror-collab": "^1.3.1", "prosemirror-commands": "^1.6.2", "prosemirror-dropcursor": "^1.8.1", "prosemirror-gapcursor": "^1.3.2", "prosemirror-history": "^1.4.1", - "prosemirror-inputrules": "^1.4.0", "prosemirror-keymap": "^1.2.2", - "prosemirror-markdown": "^1.13.1", - "prosemirror-menu": "^1.2.4", "prosemirror-model": "^1.24.1", - "prosemirror-schema-basic": "^1.2.3", "prosemirror-schema-list": "^1.5.0", "prosemirror-state": "^1.4.3", "prosemirror-tables": "^1.6.4", - "prosemirror-trailing-node": "^3.0.0", "prosemirror-transform": "^1.10.2", "prosemirror-view": "^1.38.1" }, @@ -3666,10 +3786,9 @@ } }, "node_modules/@tiptap/react": { - "version": "3.19.0", - "resolved": "https://registry.npmjs.org/@tiptap/react/-/react-3.19.0.tgz", - "integrity": "sha512-GQQMUUXMpNd8tRjc1jDK3tDRXFugJO7C928EqmeBcBzTKDrFIJ3QUoZKEPxUNb6HWhZ2WL7q00fiMzsv4DNSmg==", - "license": "MIT", + "version": "3.22.4", + "resolved": "https://registry.npmjs.org/@tiptap/react/-/react-3.22.4.tgz", + "integrity": "sha512-XIQZPwLakR1t8+Q1UeCpr+kUHDWxpJzGy9r2xUi3mpPd6Wh8dtNltScBkUlCcr0sqc6J1GF6Is02JJVQGmCZMA==", "dependencies": { "@types/use-sync-external-store": "^0.0.6", "fast-equals": "^5.3.3", @@ -3680,12 +3799,12 @@ "url": "https://github.com/sponsors/ueberdosis" }, "optionalDependencies": { - "@tiptap/extension-bubble-menu": "^3.19.0", - "@tiptap/extension-floating-menu": "^3.19.0" + "@tiptap/extension-bubble-menu": "^3.22.4", + "@tiptap/extension-floating-menu": "^3.22.4" }, "peerDependencies": { - "@tiptap/core": "^3.19.0", - "@tiptap/pm": "^3.19.0", + "@tiptap/core": "3.22.4", + "@tiptap/pm": "3.22.4", "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", "@types/react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0", "react": "^17.0.0 || ^18.0.0 || ^19.0.0", @@ -3693,35 +3812,34 @@ } }, "node_modules/@tiptap/starter-kit": { - "version": "3.19.0", - "resolved": "https://registry.npmjs.org/@tiptap/starter-kit/-/starter-kit-3.19.0.tgz", - "integrity": "sha512-dTCkHEz+Y8ADxX7h+xvl6caAj+3nII/wMB1rTQchSuNKqJTOrzyUsCWm094+IoZmLT738wANE0fRIgziNHs/ug==", - "license": "MIT", - "dependencies": { - "@tiptap/core": "^3.19.0", - "@tiptap/extension-blockquote": "^3.19.0", - "@tiptap/extension-bold": "^3.19.0", - "@tiptap/extension-bullet-list": "^3.19.0", - "@tiptap/extension-code": "^3.19.0", - "@tiptap/extension-code-block": "^3.19.0", - "@tiptap/extension-document": "^3.19.0", - "@tiptap/extension-dropcursor": "^3.19.0", - "@tiptap/extension-gapcursor": "^3.19.0", - "@tiptap/extension-hard-break": "^3.19.0", - "@tiptap/extension-heading": "^3.19.0", - "@tiptap/extension-horizontal-rule": "^3.19.0", - "@tiptap/extension-italic": "^3.19.0", - "@tiptap/extension-link": "^3.19.0", - "@tiptap/extension-list": "^3.19.0", - "@tiptap/extension-list-item": "^3.19.0", - "@tiptap/extension-list-keymap": "^3.19.0", - "@tiptap/extension-ordered-list": "^3.19.0", - "@tiptap/extension-paragraph": "^3.19.0", - "@tiptap/extension-strike": "^3.19.0", - "@tiptap/extension-text": "^3.19.0", - "@tiptap/extension-underline": "^3.19.0", - "@tiptap/extensions": "^3.19.0", - "@tiptap/pm": "^3.19.0" + "version": "3.22.4", + "resolved": "https://registry.npmjs.org/@tiptap/starter-kit/-/starter-kit-3.22.4.tgz", + "integrity": "sha512-qWjw+vfdin1rzMRpRU4cC5tLTwMJtUpXeQukv+6mOqqvhptuwuZBjUHImVEJaSPoHXS7+1ut+nTnrLyWyEuE5Q==", + "dependencies": { + "@tiptap/core": "^3.22.4", + "@tiptap/extension-blockquote": "^3.22.4", + "@tiptap/extension-bold": "^3.22.4", + "@tiptap/extension-bullet-list": "^3.22.4", + "@tiptap/extension-code": "^3.22.4", + "@tiptap/extension-code-block": "^3.22.4", + "@tiptap/extension-document": "^3.22.4", + "@tiptap/extension-dropcursor": "^3.22.4", + "@tiptap/extension-gapcursor": "^3.22.4", + "@tiptap/extension-hard-break": "^3.22.4", + "@tiptap/extension-heading": "^3.22.4", + "@tiptap/extension-horizontal-rule": "^3.22.4", + "@tiptap/extension-italic": "^3.22.4", + "@tiptap/extension-link": "^3.22.4", + "@tiptap/extension-list": "^3.22.4", + "@tiptap/extension-list-item": "^3.22.4", + "@tiptap/extension-list-keymap": "^3.22.4", + "@tiptap/extension-ordered-list": "^3.22.4", + "@tiptap/extension-paragraph": "^3.22.4", + "@tiptap/extension-strike": "^3.22.4", + "@tiptap/extension-text": "^3.22.4", + "@tiptap/extension-underline": "^3.22.4", + "@tiptap/extensions": "^3.22.4", + "@tiptap/pm": "^3.22.4" }, "funding": { "type": "github", @@ -3729,17 +3847,16 @@ } }, "node_modules/@tiptap/suggestion": { - "version": "3.20.1", - "resolved": "https://registry.npmjs.org/@tiptap/suggestion/-/suggestion-3.20.1.tgz", - "integrity": "sha512-ng7olbzgZhWvPJVJygNQK5153CjquR2eJXpkLq7bRjHlahvt4TH4tGFYvGdYZcXuzbe2g9RoqT7NaPGL9CUq9w==", - "license": "MIT", + "version": "3.22.4", + "resolved": "https://registry.npmjs.org/@tiptap/suggestion/-/suggestion-3.22.4.tgz", + "integrity": "sha512-1buvLZemITTeKmPf2wGFWvvhRFKjdQ+JgMqc67xBraOKeDd8wQi1e2XlhCYAtlVMm5f6j+qlLC/MvwuHI2jHeQ==", "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" }, "peerDependencies": { - "@tiptap/core": "^3.20.1", - "@tiptap/pm": "^3.20.1" + "@tiptap/core": "3.22.4", + "@tiptap/pm": "3.22.4" } }, "node_modules/@types/aria-query": { @@ -3747,14 +3864,53 @@ "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz", "integrity": "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==", "dev": true, - "license": "MIT", "peer": true }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "dev": true, + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", + "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", + "dev": true, + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "dev": true, + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz", + "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==", + "dev": true, + "dependencies": { + "@babel/types": "^7.28.2" + } + }, "node_modules/@types/debug": { - "version": "4.1.12", - "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz", - "integrity": "sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==", - "license": "MIT", + "version": "4.1.13", + "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.13.tgz", + "integrity": "sha512-KSVgmQmzMwPlmtljOomayoR89W4FynCAi3E8PPs7vmDVPe84hT+vGPKkJfThkmXs0x0jAaa9U8uW8bbfyS2fWw==", "dependencies": { "@types/ms": "*" } @@ -3762,14 +3918,12 @@ "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==", - "license": "MIT" + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==" }, "node_modules/@types/estree-jsx": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/@types/estree-jsx/-/estree-jsx-1.0.5.tgz", "integrity": "sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg==", - "license": "MIT", "dependencies": { "@types/estree": "*" } @@ -3778,7 +3932,6 @@ "version": "3.0.4", "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz", "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==", - "license": "MIT", "dependencies": { "@types/unist": "*" } @@ -3787,52 +3940,26 @@ "version": "7.0.15", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/linkify-it": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/@types/linkify-it/-/linkify-it-5.0.0.tgz", - "integrity": "sha512-sVDA58zAw4eWAffKOaQH5/5j3XeayukzDk+ewSsnv3p4yJEZHCCzMDiZM8e0OUrRvmpGZ85jf4yDHkHsgBNr9Q==", - "license": "MIT" - }, - "node_modules/@types/markdown-it": { - "version": "14.1.2", - "resolved": "https://registry.npmjs.org/@types/markdown-it/-/markdown-it-14.1.2.tgz", - "integrity": "sha512-promo4eFwuiW+TfGxhi+0x3czqTYJkG8qB17ZUJiVF10Xm7NLVRSLUsfRTU/6h1e24VvRnXCx+hG7li58lkzog==", - "license": "MIT", - "dependencies": { - "@types/linkify-it": "^5", - "@types/mdurl": "^2" - } + "dev": true }, "node_modules/@types/mdast": { "version": "4.0.4", "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-4.0.4.tgz", "integrity": "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==", - "license": "MIT", "dependencies": { "@types/unist": "*" } }, - "node_modules/@types/mdurl": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@types/mdurl/-/mdurl-2.0.0.tgz", - "integrity": "sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg==", - "license": "MIT" - }, "node_modules/@types/ms": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz", - "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==", - "license": "MIT" + "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==" }, "node_modules/@types/node": { - "version": "24.10.13", - "resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.13.tgz", - "integrity": "sha512-oH72nZRfDv9lADUBSo104Aq7gPHpQZc4BTx38r9xf9pg5LfP6EzSyH2n7qFmmxRQXh7YlUXODcYsg6PuTDSxGg==", + "version": "24.12.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.12.2.tgz", + "integrity": "sha512-A1sre26ke7HDIuY/M23nd9gfB+nrmhtYyMINbjI1zHJxYteKR6qSMX56FsmjMcDb3SMcjJg5BiRRgOCC/yBD0g==", "dev": true, - "license": "MIT", "dependencies": { "undici-types": "~7.16.0" } @@ -3840,14 +3967,12 @@ "node_modules/@types/prismjs": { "version": "1.26.6", "resolved": "https://registry.npmjs.org/@types/prismjs/-/prismjs-1.26.6.tgz", - "integrity": "sha512-vqlvI7qlMvcCBbVe0AKAb4f97//Hy0EBTaiW8AalRnG/xAN5zOiWWyrNqNXeq8+KAuvRewjCVY1+IPxk4RdNYw==", - "license": "MIT" + "integrity": "sha512-vqlvI7qlMvcCBbVe0AKAb4f97//Hy0EBTaiW8AalRnG/xAN5zOiWWyrNqNXeq8+KAuvRewjCVY1+IPxk4RdNYw==" }, "node_modules/@types/react": { "version": "19.2.14", "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.14.tgz", "integrity": "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==", - "license": "MIT", "dependencies": { "csstype": "^3.2.2" } @@ -3856,50 +3981,39 @@ "version": "19.2.3", "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.3.tgz", "integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==", - "license": "MIT", "peerDependencies": { "@types/react": "^19.2.0" } }, - "node_modules/@types/stylis": { - "version": "4.2.7", - "resolved": "https://registry.npmjs.org/@types/stylis/-/stylis-4.2.7.tgz", - "integrity": "sha512-VgDNokpBoKF+wrdvhAAfS55OMQpL6QRglwTwNC3kIgBrzZxA4WsFj+2eLfEA/uMUDzBcEhYmjSbwQakn/i3ajA==", - "license": "MIT" - }, "node_modules/@types/turndown": { "version": "5.0.6", "resolved": "https://registry.npmjs.org/@types/turndown/-/turndown-5.0.6.tgz", - "integrity": "sha512-ru00MoyeeouE5BX4gRL+6m/BsDfbRayOskWqUvh7CLGW+UXxHQItqALa38kKnOiZPqJrtzJUgAC2+F0rL1S4Pg==", - "license": "MIT" + "integrity": "sha512-ru00MoyeeouE5BX4gRL+6m/BsDfbRayOskWqUvh7CLGW+UXxHQItqALa38kKnOiZPqJrtzJUgAC2+F0rL1S4Pg==" }, "node_modules/@types/unist": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", - "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", - "license": "MIT" + "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==" }, "node_modules/@types/use-sync-external-store": { "version": "0.0.6", "resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.6.tgz", - "integrity": "sha512-zFDAD+tlpf2r4asuHEj0XH6pY6i0g5NeAHPn+15wk3BV6JA69eERFXC1gyGThDkVa1zCyKr5jox1+2LbV/AMLg==", - "license": "MIT" + "integrity": "sha512-zFDAD+tlpf2r4asuHEj0XH6pY6i0g5NeAHPn+15wk3BV6JA69eERFXC1gyGThDkVa1zCyKr5jox1+2LbV/AMLg==" }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.55.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.55.0.tgz", - "integrity": "sha512-1y/MVSz0NglV1ijHC8OT49mPJ4qhPYjiK08YUQVbIOyu+5k862LKUHFkpKHWu//zmr7hDR2rhwUm6gnCGNmGBQ==", + "version": "8.59.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.59.0.tgz", + "integrity": "sha512-HyAZtpdkgZwpq8Sz3FSUvCR4c+ScbuWa9AksK2Jweub7w4M3yTz4O11AqVJzLYjy/B9ZWPyc81I+mOdJU/bDQw==", "dev": true, - "license": "MIT", "dependencies": { "@eslint-community/regexpp": "^4.12.2", - "@typescript-eslint/scope-manager": "8.55.0", - "@typescript-eslint/type-utils": "8.55.0", - "@typescript-eslint/utils": "8.55.0", - "@typescript-eslint/visitor-keys": "8.55.0", + "@typescript-eslint/scope-manager": "8.59.0", + "@typescript-eslint/type-utils": "8.59.0", + "@typescript-eslint/utils": "8.59.0", + "@typescript-eslint/visitor-keys": "8.59.0", "ignore": "^7.0.5", "natural-compare": "^1.4.0", - "ts-api-utils": "^2.4.0" + "ts-api-utils": "^2.5.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -3909,9 +4023,9 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "@typescript-eslint/parser": "^8.55.0", - "eslint": "^8.57.0 || ^9.0.0", - "typescript": ">=4.8.4 <6.0.0" + "@typescript-eslint/parser": "^8.59.0", + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" } }, "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": { @@ -3919,22 +4033,20 @@ "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", "dev": true, - "license": "MIT", "engines": { "node": ">= 4" } }, "node_modules/@typescript-eslint/parser": { - "version": "8.55.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.55.0.tgz", - "integrity": "sha512-4z2nCSBfVIMnbuu8uinj+f0o4qOeggYJLbjpPHka3KH1om7e+H9yLKTYgksTaHcGco+NClhhY2vyO3HsMH1RGw==", + "version": "8.59.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.59.0.tgz", + "integrity": "sha512-TI1XGwKbDpo9tRW8UDIXCOeLk55qe9ZFGs8MTKU6/M08HWTw52DD/IYhfQtOEhEdPhLMT26Ka/x7p70nd3dzDg==", "dev": true, - "license": "MIT", "dependencies": { - "@typescript-eslint/scope-manager": "8.55.0", - "@typescript-eslint/types": "8.55.0", - "@typescript-eslint/typescript-estree": "8.55.0", - "@typescript-eslint/visitor-keys": "8.55.0", + "@typescript-eslint/scope-manager": "8.59.0", + "@typescript-eslint/types": "8.59.0", + "@typescript-eslint/typescript-estree": "8.59.0", + "@typescript-eslint/visitor-keys": "8.59.0", "debug": "^4.4.3" }, "engines": { @@ -3945,19 +4057,18 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0", - "typescript": ">=4.8.4 <6.0.0" + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" } }, "node_modules/@typescript-eslint/project-service": { - "version": "8.55.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.55.0.tgz", - "integrity": "sha512-zRcVVPFUYWa3kNnjaZGXSu3xkKV1zXy8M4nO/pElzQhFweb7PPtluDLQtKArEOGmjXoRjnUZ29NjOiF0eCDkcQ==", + "version": "8.59.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.59.0.tgz", + "integrity": "sha512-Lw5ITrR5s5TbC19YSvlr63ZfLaJoU6vtKTHyB0GQOpX0W7d5/Ir6vUahWi/8Sps/nOukZQ0IB3SmlxZnjaKVnw==", "dev": true, - "license": "MIT", "dependencies": { - "@typescript-eslint/tsconfig-utils": "^8.55.0", - "@typescript-eslint/types": "^8.55.0", + "@typescript-eslint/tsconfig-utils": "^8.59.0", + "@typescript-eslint/types": "^8.59.0", "debug": "^4.4.3" }, "engines": { @@ -3968,18 +4079,17 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "typescript": ">=4.8.4 <6.0.0" + "typescript": ">=4.8.4 <6.1.0" } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "8.55.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.55.0.tgz", - "integrity": "sha512-fVu5Omrd3jeqeQLiB9f1YsuK/iHFOwb04bCtY4BSCLgjNbOD33ZdV6KyEqplHr+IlpgT0QTZ/iJ+wT7hvTx49Q==", + "version": "8.59.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.59.0.tgz", + "integrity": "sha512-UzR16Ut8IpA3Mc4DbgAShlPPkVm8xXMWafXxB0BocaVRHs8ZGakAxGRskF7FId3sdk9lgGD73GSFaWmWFDE4dg==", "dev": true, - "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.55.0", - "@typescript-eslint/visitor-keys": "8.55.0" + "@typescript-eslint/types": "8.59.0", + "@typescript-eslint/visitor-keys": "8.59.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -3990,11 +4100,10 @@ } }, "node_modules/@typescript-eslint/tsconfig-utils": { - "version": "8.55.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.55.0.tgz", - "integrity": "sha512-1R9cXqY7RQd7WuqSN47PK9EDpgFUK3VqdmbYrvWJZYDd0cavROGn+74ktWBlmJ13NXUQKlZ/iAEQHI/V0kKe0Q==", + "version": "8.59.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.59.0.tgz", + "integrity": "sha512-91Sbl3s4Kb3SybliIY6muFBmHVv+pYXfybC4Oolp3dvk8BvIE3wOPc+403CWIT7mJNkfQRGtdqghzs2+Z91Tqg==", "dev": true, - "license": "MIT", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, @@ -4003,21 +4112,20 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "typescript": ">=4.8.4 <6.0.0" + "typescript": ">=4.8.4 <6.1.0" } }, "node_modules/@typescript-eslint/type-utils": { - "version": "8.55.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.55.0.tgz", - "integrity": "sha512-x1iH2unH4qAt6I37I2CGlsNs+B9WGxurP2uyZLRz6UJoZWDBx9cJL1xVN/FiOmHEONEg6RIufdvyT0TEYIgC5g==", + "version": "8.59.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.59.0.tgz", + "integrity": "sha512-3TRiZaQSltGqGeNrJzzr1+8YcEobKH9rHnqIp/1psfKFmhRQDNMGP5hBufanYTGznwShzVLs3Mz+gDN7HkWfXg==", "dev": true, - "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.55.0", - "@typescript-eslint/typescript-estree": "8.55.0", - "@typescript-eslint/utils": "8.55.0", + "@typescript-eslint/types": "8.59.0", + "@typescript-eslint/typescript-estree": "8.59.0", + "@typescript-eslint/utils": "8.59.0", "debug": "^4.4.3", - "ts-api-utils": "^2.4.0" + "ts-api-utils": "^2.5.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -4027,16 +4135,15 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0", - "typescript": ">=4.8.4 <6.0.0" + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" } }, "node_modules/@typescript-eslint/types": { - "version": "8.55.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.55.0.tgz", - "integrity": "sha512-ujT0Je8GI5BJWi+/mMoR0wxwVEQaxM+pi30xuMiJETlX80OPovb2p9E8ss87gnSVtYXtJoU9U1Cowcr6w2FE0w==", + "version": "8.59.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.59.0.tgz", + "integrity": "sha512-nLzdsT1gdOgFxxxwrlNVUBzSNBEEHJ86bblmk4QAS6stfig7rcJzWKqCyxFy3YRRHXDWEkb2NralA1nOYkkm/A==", "dev": true, - "license": "MIT", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, @@ -4046,21 +4153,20 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.55.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.55.0.tgz", - "integrity": "sha512-EwrH67bSWdx/3aRQhCoxDaHM+CrZjotc2UCCpEDVqfCE+7OjKAGWNY2HsCSTEVvWH2clYQK8pdeLp42EVs+xQw==", + "version": "8.59.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.59.0.tgz", + "integrity": "sha512-O9Re9P1BmBLFJyikRbQpLku/QA3/AueZNO9WePLBwQrvkixTmDe8u76B6CYUAITRl/rHawggEqUGn5QIkVRLMw==", "dev": true, - "license": "MIT", "dependencies": { - "@typescript-eslint/project-service": "8.55.0", - "@typescript-eslint/tsconfig-utils": "8.55.0", - "@typescript-eslint/types": "8.55.0", - "@typescript-eslint/visitor-keys": "8.55.0", + "@typescript-eslint/project-service": "8.59.0", + "@typescript-eslint/tsconfig-utils": "8.59.0", + "@typescript-eslint/types": "8.59.0", + "@typescript-eslint/visitor-keys": "8.59.0", "debug": "^4.4.3", - "minimatch": "^9.0.5", + "minimatch": "^10.2.2", "semver": "^7.7.3", "tinyglobby": "^0.2.15", - "ts-api-utils": "^2.4.0" + "ts-api-utils": "^2.5.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -4070,46 +4176,67 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "typescript": ">=4.8.4 <6.0.0" + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true, + "engines": { + "node": "18 || 20 || >=22" } }, "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", - "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz", + "integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==", "dev": true, - "license": "MIT", "dependencies": { - "balanced-match": "^1.0.0" + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" } }, "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "version": "10.2.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", + "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", "dev": true, - "license": "ISC", "dependencies": { - "brace-expansion": "^2.0.1" + "brace-expansion": "^5.0.5" }, "engines": { - "node": ">=16 || 14 >=14.17" + "node": "18 || 20 || >=22" }, "funding": { "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/@typescript-eslint/utils": { - "version": "8.55.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.55.0.tgz", - "integrity": "sha512-BqZEsnPGdYpgyEIkDC1BadNY8oMwckftxBT+C8W0g1iKPdeqKZBtTfnvcq0nf60u7MkjFO8RBvpRGZBPw4L2ow==", + "version": "8.59.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.59.0.tgz", + "integrity": "sha512-I1R/K7V07XsMJ12Oaxg/O9GfrysGTmCRhvZJBv0RE0NcULMzjqVpR5kRRQjHsz3J/bElU7HwCO7zkqL+MSUz+g==", "dev": true, - "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.9.1", - "@typescript-eslint/scope-manager": "8.55.0", - "@typescript-eslint/types": "8.55.0", - "@typescript-eslint/typescript-estree": "8.55.0" + "@typescript-eslint/scope-manager": "8.59.0", + "@typescript-eslint/types": "8.59.0", + "@typescript-eslint/typescript-estree": "8.59.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -4119,19 +4246,18 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0", - "typescript": ">=4.8.4 <6.0.0" + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.55.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.55.0.tgz", - "integrity": "sha512-AxNRwEie8Nn4eFS1FzDMJWIISMGoXMb037sgCBJ3UR6o0fQTzr2tqN9WT+DkWJPhIdQCfV7T6D387566VtnCJA==", + "version": "8.59.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.59.0.tgz", + "integrity": "sha512-/uejZt4dSere1bx12WLlPfv8GktzcaDtuJ7s42/HEZ5zGj9oxRaD4bj7qwSunXkf+pbAhFt2zjpHYUiT5lHf0Q==", "dev": true, - "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.55.0", - "eslint-visitor-keys": "^4.2.1" + "@typescript-eslint/types": "8.59.0", + "eslint-visitor-keys": "^5.0.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -4141,28 +4267,38 @@ "url": "https://opencollective.com/typescript-eslint" } }, + "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", + "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==", + "dev": true, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, "node_modules/@uiw/copy-to-clipboard": { "version": "1.0.20", "resolved": "https://registry.npmjs.org/@uiw/copy-to-clipboard/-/copy-to-clipboard-1.0.20.tgz", "integrity": "sha512-IFQhS62CLNon1YgYJTEzXR2N3WVXg7V1FaBRDLMlzU6JY5X6Hr3OPAcw4WNoKcz2XcFD6XCgwEjlsmj+JA0mWA==", - "license": "MIT", "funding": { "url": "https://jaywcjlove.github.io/#/sponsor" } }, "node_modules/@uiw/react-markdown-preview": { - "version": "5.1.5", - "resolved": "https://registry.npmjs.org/@uiw/react-markdown-preview/-/react-markdown-preview-5.1.5.tgz", - "integrity": "sha512-DNOqx1a6gJR7Btt57zpGEKTfHRlb7rWbtctMRO2f82wWcuoJsxPBrM+JWebDdOD0LfD8oe2CQvW2ICQJKHQhZg==", - "license": "MIT", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@uiw/react-markdown-preview/-/react-markdown-preview-5.2.0.tgz", + "integrity": "sha512-39kgf+Wk6DXpYtztUt34xFPt0BzGkuxmFZKI7rNAlCFKXdAmyhqLlRbQKyrRwOVeuRyrQy/Z96UBSw5AHFBivg==", "dependencies": { "@babel/runtime": "^7.17.2", "@uiw/copy-to-clipboard": "~1.0.12", - "react-markdown": "~9.0.1", - "rehype-attr": "~3.0.1", + "react-markdown": "~10.1.0", + "rehype-attr": "~4.0.0", "rehype-autolink-headings": "~7.1.0", "rehype-ignore": "^2.0.0", - "rehype-prism-plus": "2.0.0", + "rehype-prism-plus": "~2.0.0", "rehype-raw": "^7.0.0", "rehype-rewrite": "~4.0.0", "rehype-slug": "~6.0.0", @@ -4178,99 +4314,13 @@ "react-dom": ">=16.8.0" } }, - "node_modules/@uiw/react-markdown-preview/node_modules/@types/hast": { - "version": "2.3.10", - "resolved": "https://registry.npmjs.org/@types/hast/-/hast-2.3.10.tgz", - "integrity": "sha512-McWspRw8xx8J9HurkVBfYj0xKoE25tOFlHGdx4MJ5xORQrMGZNqJhVQWaIbm6Oyla5kYOXtDiopzKRJzEOkwJw==", - "license": "MIT", - "dependencies": { - "@types/unist": "^2" - } - }, - "node_modules/@uiw/react-markdown-preview/node_modules/@types/unist": { - "version": "2.0.11", - "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.11.tgz", - "integrity": "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==", - "license": "MIT" - }, - "node_modules/@uiw/react-markdown-preview/node_modules/hast-util-parse-selector": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/hast-util-parse-selector/-/hast-util-parse-selector-3.1.1.tgz", - "integrity": "sha512-jdlwBjEexy1oGz0aJ2f4GKMaVKkA9jwjr4MjAAI22E5fM/TXVZHuS5OpONtdeIkRKqAaryQ2E9xNQxijoThSZA==", - "license": "MIT", - "dependencies": { - "@types/hast": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/@uiw/react-markdown-preview/node_modules/hastscript": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/hastscript/-/hastscript-7.2.0.tgz", - "integrity": "sha512-TtYPq24IldU8iKoJQqvZOuhi5CyCQRAbvDOX0x1eW6rsHSxa/1i2CCiptNTotGHJ3VoHRGmqiv6/D3q113ikkw==", - "license": "MIT", - "dependencies": { - "@types/hast": "^2.0.0", - "comma-separated-tokens": "^2.0.0", - "hast-util-parse-selector": "^3.0.0", - "property-information": "^6.0.0", - "space-separated-tokens": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/@uiw/react-markdown-preview/node_modules/property-information": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/property-information/-/property-information-6.5.0.tgz", - "integrity": "sha512-PgTgs/BlvHxOu8QuEN7wi5A0OmXaBcHpmCSTehcs6Uuu9IkDIEo13Hy7n898RHfrQ49vKCoGeWZSaAK01nwVig==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/@uiw/react-markdown-preview/node_modules/refractor": { - "version": "4.9.0", - "resolved": "https://registry.npmjs.org/refractor/-/refractor-4.9.0.tgz", - "integrity": "sha512-nEG1SPXFoGGx+dcjftjv8cAjEusIh6ED1xhf5DG3C0x/k+rmZ2duKnc3QLpt6qeHv5fPb8uwN3VWN2BT7fr3Og==", - "license": "MIT", - "dependencies": { - "@types/hast": "^2.0.0", - "@types/prismjs": "^1.0.0", - "hastscript": "^7.0.0", - "parse-entities": "^4.0.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/@uiw/react-markdown-preview/node_modules/rehype-prism-plus": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/rehype-prism-plus/-/rehype-prism-plus-2.0.0.tgz", - "integrity": "sha512-FeM/9V2N7EvDZVdR2dqhAzlw5YI49m9Tgn7ZrYJeYHIahM6gcXpH0K1y2gNnKanZCydOMluJvX2cB9z3lhY8XQ==", - "license": "MIT", - "dependencies": { - "hast-util-to-string": "^3.0.0", - "parse-numeric-range": "^1.3.0", - "refractor": "^4.8.0", - "rehype-parse": "^9.0.0", - "unist-util-filter": "^5.0.0", - "unist-util-visit": "^5.0.0" - } - }, "node_modules/@uiw/react-md-editor": { - "version": "4.0.11", - "resolved": "https://registry.npmjs.org/@uiw/react-md-editor/-/react-md-editor-4.0.11.tgz", - "integrity": "sha512-F0OR5O1v54EkZYvJj3ew0I7UqLiPeU34hMAY4MdXS3hI86rruYi5DHVkG/VuvLkUZW7wIETM2QFtZ459gKIjQA==", - "license": "MIT", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@uiw/react-md-editor/-/react-md-editor-4.1.0.tgz", + "integrity": "sha512-ti8WO9Mf55bFdb5JXEJmQ77NQRG8VyUAuE6c5XRwMI3S05Q0uuiRKOs5yAyJWiISgkV39DMmf4UU8DbADPILhw==", "dependencies": { "@babel/runtime": "^7.14.6", - "@uiw/react-markdown-preview": "^5.0.6", + "@uiw/react-markdown-preview": "^5.2.0", "rehype": "~13.0.0", "rehype-prism-plus": "~2.0.0" }, @@ -4285,8 +4335,27 @@ "node_modules/@ungap/structured-clone": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz", - "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==", - "license": "ISC" + "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==" + }, + "node_modules/@vitejs/plugin-react": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.7.0.tgz", + "integrity": "sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==", + "dev": true, + "dependencies": { + "@babel/core": "^7.28.0", + "@babel/plugin-transform-react-jsx-self": "^7.27.1", + "@babel/plugin-transform-react-jsx-source": "^7.27.1", + "@rolldown/pluginutils": "1.0.0-beta.27", + "@types/babel__core": "^7.20.5", + "react-refresh": "^0.17.0" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "peerDependencies": { + "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" + } }, "node_modules/@vexaai/transcript-rendering": { "version": "0.4.0", @@ -4295,28 +4364,32 @@ "license": "Apache-2.0" }, "node_modules/@vitejs/plugin-react-swc": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/@vitejs/plugin-react-swc/-/plugin-react-swc-4.2.3.tgz", - "integrity": "sha512-QIluDil2prhY1gdA3GGwxZzTAmLdi8cQ2CcuMW4PB/Wu4e/1pzqrwhYWVd09LInCRlDUidQjd0B70QWbjWtLxA==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react-swc/-/plugin-react-swc-4.3.0.tgz", + "integrity": "sha512-mOkXCII839dHyAt/gpoSlm28JIVDwhZ6tnG6wJxUy2bmOx7UaPjvOyIDf3SFv5s7Eo7HVaq6kRcu6YMEzt5Z7w==", "dev": true, - "license": "MIT", "dependencies": { - "@rolldown/pluginutils": "1.0.0-rc.2", + "@rolldown/pluginutils": "1.0.0-rc.7", "@swc/core": "^1.15.11" }, "engines": { "node": "^20.19.0 || >=22.12.0" }, "peerDependencies": { - "vite": "^4 || ^5 || ^6 || ^7" + "vite": "^4 || ^5 || ^6 || ^7 || ^8" } }, + "node_modules/@vitejs/plugin-react-swc/node_modules/@rolldown/pluginutils": { + "version": "1.0.0-rc.7", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.7.tgz", + "integrity": "sha512-qujRfC8sFVInYSPPMLQByRh7zhwkGFS4+tyMQ83srV1qrxL4g8E2tyxVVyxd0+8QeBM1mIk9KbWxkegRr76XzA==", + "dev": true + }, "node_modules/@vitest/coverage-v8": { "version": "2.1.9", "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-2.1.9.tgz", "integrity": "sha512-Z2cOr0ksM00MpEfyVE8KXIYPEcBFxdbLSs56L8PO0QQMxt/6bDj45uQfxoc96v05KW3clk7vvgP0qfDit9DmfQ==", "dev": true, - "license": "MIT", "dependencies": { "@ampproject/remapping": "^2.3.0", "@bcoe/v8-coverage": "^0.2.3", @@ -4349,7 +4422,6 @@ "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-2.1.9.tgz", "integrity": "sha512-UJCIkTBenHeKT1TTlKMJWy1laZewsRIzYighyYiJKZreqtdxSos/S1t+ktRMQWu2CKqaarrkeszJx1cgC5tGZw==", "dev": true, - "license": "MIT", "dependencies": { "@vitest/spy": "2.1.9", "@vitest/utils": "2.1.9", @@ -4365,7 +4437,6 @@ "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-2.1.9.tgz", "integrity": "sha512-tVL6uJgoUdi6icpxmdrn5YNo3g3Dxv+IHJBr0GXHaEdTcw3F+cPKnsXFhli6nO+f/6SDKPHEK1UN+k+TQv0Ehg==", "dev": true, - "license": "MIT", "dependencies": { "@vitest/spy": "2.1.9", "estree-walker": "^3.0.3", @@ -4392,7 +4463,6 @@ "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-2.1.9.tgz", "integrity": "sha512-KhRIdGV2U9HOUzxfiHmY8IFHTdqtOhIzCpd8WRdJiE7D/HUcZVD0EgQCVjm+Q9gkUXWgBvMmTtZgIG48wq7sOQ==", "dev": true, - "license": "MIT", "dependencies": { "tinyrainbow": "^1.2.0" }, @@ -4405,7 +4475,6 @@ "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-2.1.9.tgz", "integrity": "sha512-ZXSSqTFIrzduD63btIfEyOmNcBmQvgOVsPNPe0jYtESiXkhd8u2erDLnMxmGrDCwHCCHE7hxwRDCT3pt0esT4g==", "dev": true, - "license": "MIT", "dependencies": { "@vitest/utils": "2.1.9", "pathe": "^1.1.2" @@ -4419,7 +4488,6 @@ "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-2.1.9.tgz", "integrity": "sha512-oBO82rEjsxLNJincVhLhaxxZdEtV0EFHMK5Kmx5sJ6H9L183dHECjiefOAdnqpIgT5eZwT04PoggUnW88vOBNQ==", "dev": true, - "license": "MIT", "dependencies": { "@vitest/pretty-format": "2.1.9", "magic-string": "^0.30.12", @@ -4434,7 +4502,6 @@ "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-2.1.9.tgz", "integrity": "sha512-E1B35FwzXXTs9FHNK6bDszs7mtydNi5MIfUWpceJ8Xbfb1gBMscAnwLbEu+B44ed6W3XjL9/ehLPHR1fkf1KLQ==", "dev": true, - "license": "MIT", "dependencies": { "tinyspy": "^3.0.2" }, @@ -4447,7 +4514,6 @@ "resolved": "https://registry.npmjs.org/@vitest/ui/-/ui-2.1.9.tgz", "integrity": "sha512-izzd2zmnk8Nl5ECYkW27328RbQ1nKvkm6Bb5DAaz1Gk59EbLkiCMa6OLT0NoaAYTjOFS6N+SMYW1nh4/9ljPiw==", "dev": true, - "license": "MIT", "dependencies": { "@vitest/utils": "2.1.9", "fflate": "^0.8.2", @@ -4469,7 +4535,6 @@ "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-2.1.9.tgz", "integrity": "sha512-v0psaMSkNJ3A2NMrUEHFRzJtDPFn+/VWZ5WxImB21T9fjucJRmS7xCS3ppEnARb9y11OAzaD+P2Ps+b+BGX5iQ==", "dev": true, - "license": "MIT", "dependencies": { "@vitest/pretty-format": "2.1.9", "loupe": "^3.1.2", @@ -4480,11 +4545,10 @@ } }, "node_modules/acorn": { - "version": "8.15.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", - "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", + "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" }, @@ -4497,7 +4561,6 @@ "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", "dev": true, - "license": "MIT", "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } @@ -4507,17 +4570,15 @@ "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", "dev": true, - "license": "MIT", "engines": { "node": ">= 14" } }, "node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.15.0.tgz", + "integrity": "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==", "dev": true, - "license": "MIT", "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -4534,7 +4595,6 @@ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", "dev": true, - "license": "MIT", "engines": { "node": ">=8" } @@ -4544,7 +4604,6 @@ "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" }, @@ -4559,13 +4618,12 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "license": "Python-2.0" + "dev": true }, "node_modules/aria-hidden": { "version": "1.2.6", "resolved": "https://registry.npmjs.org/aria-hidden/-/aria-hidden-1.2.6.tgz", "integrity": "sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA==", - "license": "MIT", "dependencies": { "tslib": "^2.0.0" }, @@ -4578,7 +4636,6 @@ "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.0.tgz", "integrity": "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==", "dev": true, - "license": "Apache-2.0", "dependencies": { "dequal": "^2.0.3" } @@ -4588,7 +4645,6 @@ "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", "dev": true, - "license": "MIT", "engines": { "node": ">=12" } @@ -4596,25 +4652,22 @@ "node_modules/asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", - "license": "MIT" + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" }, "node_modules/axios": { - "version": "1.13.5", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.13.5.tgz", - "integrity": "sha512-cz4ur7Vb0xS4/KUN0tPWe44eqxrIu31me+fbang3ijiNscE129POzipJJA6zniq2C/Z6sJCjMimjS8Lc/GAs8Q==", - "license": "MIT", + "version": "1.15.2", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.15.2.tgz", + "integrity": "sha512-wLrXxPtcrPTsNlJmKjkPnNPK2Ihe0hn0wGSaTEiHRPxwjvJwT3hKmXF4dpqxmPO9SoNb2FsYXj/xEo0gHN+D5A==", "dependencies": { "follow-redirects": "^1.15.11", "form-data": "^4.0.5", - "proxy-from-env": "^1.1.0" + "proxy-from-env": "^2.1.0" } }, "node_modules/bail": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/bail/-/bail-2.0.2.tgz", "integrity": "sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==", - "license": "MIT", "funding": { "type": "github", "url": "https://github.com/sponsors/wooorm" @@ -4624,14 +4677,24 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "node_modules/baseline-browser-mapping": { + "version": "2.10.23", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.23.tgz", + "integrity": "sha512-xwVXGqevyKPsiuQdLj+dZMVjidjJV508TBqexND5HrF89cGdCYCJFB3qhcxRHSeMctdCfbR1jrxBajhDy7o29g==", "dev": true, - "license": "MIT" + "bin": { + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" + } }, "node_modules/bcp-47-match": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/bcp-47-match/-/bcp-47-match-2.0.3.tgz", "integrity": "sha512-JtTezzbAibu8G0R9op9zb3vcWZd9JF6M0xOYGPn0fNCd7wOpRB1mU2mH9T8gaBGbAAyIIVgB2G7xG0GP98zMAQ==", - "license": "MIT", "funding": { "type": "github", "url": "https://github.com/sponsors/wooorm" @@ -4640,26 +4703,56 @@ "node_modules/boolbase": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", - "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", - "license": "ISC" + "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==" }, "node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz", + "integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==", "dev": true, - "license": "MIT", "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, + "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" + } + ], + "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/cac": { "version": "6.7.14", "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==", "dev": true, - "license": "MIT", "engines": { "node": ">=8" } @@ -4668,7 +4761,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", - "license": "MIT", "dependencies": { "es-errors": "^1.3.0", "function-bind": "^1.1.2" @@ -4682,7 +4774,6 @@ "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", "dev": true, - "license": "MIT", "engines": { "node": ">=6" } @@ -4691,16 +4782,34 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/camelize/-/camelize-1.0.1.tgz", "integrity": "sha512-dU+Tx2fsypxTgtLoE36npi3UqcjSSMNYfkqgmoEhtZrraP5VWq0K7FkWVTYa8eMPtnU/G2txVsfdCJTn9uzpuQ==", - "license": "MIT", "funding": { "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/caniuse-lite": { + "version": "1.0.30001791", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001791.tgz", + "integrity": "sha512-yk0l/YSrOnFZk3UROpDLQD9+kC1l4meK/wed583AXrzoarMGJcbRi2Q4RaUYbKxYAsZ8sWmaSa/DsLmdBeI1vQ==", + "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" + } + ] + }, "node_modules/ccount": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/ccount/-/ccount-2.0.1.tgz", "integrity": "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==", - "license": "MIT", "funding": { "type": "github", "url": "https://github.com/sponsors/wooorm" @@ -4711,7 +4820,6 @@ "resolved": "https://registry.npmjs.org/chai/-/chai-5.3.3.tgz", "integrity": "sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw==", "dev": true, - "license": "MIT", "dependencies": { "assertion-error": "^2.0.1", "check-error": "^2.1.1", @@ -4728,7 +4836,6 @@ "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dev": true, - "license": "MIT", "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -4744,7 +4851,6 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-2.0.2.tgz", "integrity": "sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==", - "license": "MIT", "funding": { "type": "github", "url": "https://github.com/sponsors/wooorm" @@ -4754,7 +4860,6 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/character-entities-html4/-/character-entities-html4-2.1.0.tgz", "integrity": "sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==", - "license": "MIT", "funding": { "type": "github", "url": "https://github.com/sponsors/wooorm" @@ -4764,7 +4869,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-3.0.0.tgz", "integrity": "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==", - "license": "MIT", "funding": { "type": "github", "url": "https://github.com/sponsors/wooorm" @@ -4774,7 +4878,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/character-reference-invalid/-/character-reference-invalid-2.0.1.tgz", "integrity": "sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==", - "license": "MIT", "funding": { "type": "github", "url": "https://github.com/sponsors/wooorm" @@ -4785,7 +4888,6 @@ "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.3.tgz", "integrity": "sha512-PAJdDJusoxnwm1VwW07VWwUN1sl7smmC3OKggvndJFadxxDRyFJBX/ggnu/KE4kQAB7a3Dp8f/YXC1FlUprWmA==", "dev": true, - "license": "MIT", "engines": { "node": ">= 16" } @@ -4793,15 +4895,13 @@ "node_modules/classnames": { "version": "2.5.1", "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.5.1.tgz", - "integrity": "sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==", - "license": "MIT" + "integrity": "sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==" }, "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" }, @@ -4813,14 +4913,12 @@ "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" + "dev": true }, "node_modules/combined-stream": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "license": "MIT", "dependencies": { "delayed-stream": "~1.0.0" }, @@ -4832,7 +4930,6 @@ "version": "2.0.3", "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz", "integrity": "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==", - "license": "MIT", "funding": { "type": "github", "url": "https://github.com/sponsors/wooorm" @@ -4842,21 +4939,19 @@ "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "dev": true, - "license": "MIT" + "dev": true }, - "node_modules/crelt": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/crelt/-/crelt-1.0.6.tgz", - "integrity": "sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g==", - "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", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true }, "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", @@ -4870,7 +4965,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/css-color-keywords/-/css-color-keywords-1.0.0.tgz", "integrity": "sha512-FyyrDHZKEjXDpNJYvVsV960FiqQyXc/LlYmsxl2BcdMb2WPx0OGRVgTg55rPSyLSNMqP52R9r8geSp7apN3Ofg==", - "license": "ISC", "engines": { "node": ">=4" } @@ -4888,14 +4982,12 @@ "type": "patreon", "url": "https://patreon.com/mdevils" } - ], - "license": "MIT" + ] }, "node_modules/css-to-react-native": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/css-to-react-native/-/css-to-react-native-3.2.0.tgz", "integrity": "sha512-e8RKaLXMOFii+02mOlqwjbD00KSEKqblnpO9e++1aXS1fPQOpS1YoqdVHBqPjHNoxeF2mimzVqawm2KCbEdtHQ==", - "license": "MIT", "dependencies": { "camelize": "^1.0.0", "css-color-keywords": "^1.0.0", @@ -4906,15 +4998,13 @@ "version": "1.5.1", "resolved": "https://registry.npmjs.org/css.escape/-/css.escape-1.5.1.tgz", "integrity": "sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/cssstyle": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-4.6.0.tgz", "integrity": "sha512-2z+rWdzbbSZv6/rhtvzvqeZQHrBaqgogqt85sqFNbabZOuFbCVFb8kPeEtZjiKkbrm395irpNKiYeFeLiQnFPg==", "dev": true, - "license": "MIT", "dependencies": { "@asamuzakjp/css-color": "^3.2.0", "rrweb-cssom": "^0.8.0" @@ -4927,21 +5017,18 @@ "version": "0.8.0", "resolved": "https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.8.0.tgz", "integrity": "sha512-guoltQEx+9aMf2gDZ0s62EcV8lsXR+0w8915TC3ITdn2YueuNjdAYh/levpU9nFaoChh9RUS5ZdQMrKfVEN9tw==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/csstype": { "version": "3.2.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", - "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", - "license": "MIT" + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==" }, "node_modules/data-urls": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-5.0.0.tgz", "integrity": "sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg==", "dev": true, - "license": "MIT", "dependencies": { "whatwg-mimetype": "^4.0.0", "whatwg-url": "^14.0.0" @@ -4954,7 +5041,6 @@ "version": "4.4.3", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", - "license": "MIT", "dependencies": { "ms": "^2.1.3" }, @@ -4971,14 +5057,12 @@ "version": "10.6.0", "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.6.0.tgz", "integrity": "sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/decode-named-character-reference": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/decode-named-character-reference/-/decode-named-character-reference-1.3.0.tgz", "integrity": "sha512-GtpQYB283KrPp6nRw50q3U9/VfOutZOe103qlN7BPP6Ad27xYnOIWv4lPzo8HCAL+mMZofJ9KEy30fq6MfaK6Q==", - "license": "MIT", "dependencies": { "character-entities": "^2.0.0" }, @@ -4992,7 +5076,6 @@ "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.2.tgz", "integrity": "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==", "dev": true, - "license": "MIT", "engines": { "node": ">=6" } @@ -5001,14 +5084,12 @@ "version": "0.1.4", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", - "license": "MIT", "engines": { "node": ">=0.4.0" } @@ -5017,7 +5098,6 @@ "version": "2.0.3", "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", - "license": "MIT", "engines": { "node": ">=6" } @@ -5025,14 +5105,12 @@ "node_modules/detect-node-es": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/detect-node-es/-/detect-node-es-1.1.0.tgz", - "integrity": "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==", - "license": "MIT" + "integrity": "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==" }, "node_modules/devlop": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/devlop/-/devlop-1.1.0.tgz", "integrity": "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==", - "license": "MIT", "dependencies": { "dequal": "^2.0.0" }, @@ -5045,7 +5123,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/direction/-/direction-2.0.1.tgz", "integrity": "sha512-9S6m9Sukh1cZNknO1CWAr2QAWsbKLafQiyM5gZ7VgXHeuaoUwffKN4q6NC4A/Mf9iiPlOXQEKW/Mv/mh9/3YFA==", - "license": "MIT", "bin": { "direction": "cli.js" }, @@ -5059,14 +5136,12 @@ "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz", "integrity": "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==", "dev": true, - "license": "MIT", "peer": true }, "node_modules/dunder-proto": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", - "license": "MIT", "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-errors": "^1.3.0", @@ -5080,21 +5155,24 @@ "version": "0.2.0", "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", - "dev": true, - "license": "MIT" + "dev": true + }, + "node_modules/electron-to-chromium": { + "version": "1.5.344", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.344.tgz", + "integrity": "sha512-4MxfbmNDm+KPh066EZy+eUnkcDPcZ35wNmOWzFuh/ijvHsve6kbLTLURy88uCNK5FbpN+yk2nQY6BYh1GEt+wg==", + "dev": true }, "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" + "dev": true }, "node_modules/entities": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", - "license": "BSD-2-Clause", "engines": { "node": ">=0.12" }, @@ -5106,7 +5184,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", - "license": "MIT", "engines": { "node": ">= 0.4" } @@ -5115,7 +5192,6 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", - "license": "MIT", "engines": { "node": ">= 0.4" } @@ -5124,14 +5200,12 @@ "version": "1.7.0", "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz", "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/es-object-atoms": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", - "license": "MIT", "dependencies": { "es-errors": "^1.3.0" }, @@ -5143,7 +5217,6 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", - "license": "MIT", "dependencies": { "es-errors": "^1.3.0", "get-intrinsic": "^1.2.6", @@ -5160,7 +5233,6 @@ "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", "dev": true, "hasInstallScript": true, - "license": "MIT", "bin": { "esbuild": "bin/esbuild" }, @@ -5193,11 +5265,20 @@ "@esbuild/win32-x64": "0.21.5" } }, + "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, + "engines": { + "node": ">=6" + } + }, "node_modules/escape-string-regexp": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "license": "MIT", + "dev": true, "engines": { "node": ">=10" }, @@ -5206,25 +5287,24 @@ } }, "node_modules/eslint": { - "version": "9.39.2", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.2.tgz", - "integrity": "sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==", + "version": "9.39.4", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.4.tgz", + "integrity": "sha512-XoMjdBOwe/esVgEvLmNsD3IRHkm7fbKIUGvrleloJXUZgDHig2IPWNniv+GwjyJXzuNqVjlr5+4yVUZjycJwfQ==", "dev": true, - "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", - "@eslint/config-array": "^0.21.1", + "@eslint/config-array": "^0.21.2", "@eslint/config-helpers": "^0.4.2", "@eslint/core": "^0.17.0", - "@eslint/eslintrc": "^3.3.1", - "@eslint/js": "9.39.2", + "@eslint/eslintrc": "^3.3.5", + "@eslint/js": "9.39.4", "@eslint/plugin-kit": "^0.4.1", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", "@types/estree": "^1.0.6", - "ajv": "^6.12.4", + "ajv": "^6.14.0", "chalk": "^4.0.0", "cross-spawn": "^7.0.6", "debug": "^4.3.2", @@ -5243,7 +5323,7 @@ "is-glob": "^4.0.0", "json-stable-stringify-without-jsonify": "^1.0.1", "lodash.merge": "^4.6.2", - "minimatch": "^3.1.2", + "minimatch": "^3.1.5", "natural-compare": "^1.4.0", "optionator": "^0.9.3" }, @@ -5270,7 +5350,6 @@ "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-5.2.0.tgz", "integrity": "sha512-+f15FfK64YQwZdJNELETdn5ibXEUQmW1DZL6KXhNnc2heoy/sg9VJJeT7n8TlMWouzWqSWavFkIhHyIbIAEapg==", "dev": true, - "license": "MIT", "engines": { "node": ">=10" }, @@ -5283,7 +5362,6 @@ "resolved": "https://registry.npmjs.org/eslint-plugin-react-refresh/-/eslint-plugin-react-refresh-0.4.26.tgz", "integrity": "sha512-1RETEylht2O6FM/MvgnyvT+8K21wLqDNg4qD51Zj3guhjt433XbnnkVttHMyaVyAFD03QSV4LPS5iE3VQmO7XQ==", "dev": true, - "license": "MIT", "peerDependencies": { "eslint": ">=8.40" } @@ -5293,7 +5371,6 @@ "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", "dev": true, - "license": "BSD-2-Clause", "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^5.2.0" @@ -5310,7 +5387,6 @@ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", "dev": true, - "license": "Apache-2.0", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, @@ -5323,7 +5399,6 @@ "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", "dev": true, - "license": "BSD-2-Clause", "dependencies": { "acorn": "^8.15.0", "acorn-jsx": "^5.3.2", @@ -5341,7 +5416,6 @@ "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz", "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==", "dev": true, - "license": "BSD-3-Clause", "dependencies": { "estraverse": "^5.1.0" }, @@ -5354,7 +5428,6 @@ "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", "dev": true, - "license": "BSD-2-Clause", "dependencies": { "estraverse": "^5.2.0" }, @@ -5367,7 +5440,6 @@ "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", "dev": true, - "license": "BSD-2-Clause", "engines": { "node": ">=4.0" } @@ -5376,7 +5448,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/estree-util-is-identifier-name/-/estree-util-is-identifier-name-3.0.0.tgz", "integrity": "sha512-hFtqIDZTIUZ9BXLb8y4pYGyk6+wekIivNVTcmvk8NoOh+VeRn5y6cEHzbURrWbfp1fIqdVipilzj+lfaadNZmg==", - "license": "MIT", "funding": { "type": "opencollective", "url": "https://opencollective.com/unified" @@ -5387,7 +5458,6 @@ "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", "dev": true, - "license": "MIT", "dependencies": { "@types/estree": "^1.0.0" } @@ -5397,7 +5467,6 @@ "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", "dev": true, - "license": "BSD-2-Clause", "engines": { "node": ">=0.10.0" } @@ -5407,7 +5476,6 @@ "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.3.0.tgz", "integrity": "sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==", "dev": true, - "license": "Apache-2.0", "engines": { "node": ">=12.0.0" } @@ -5415,21 +5483,18 @@ "node_modules/extend": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", - "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", - "license": "MIT" + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/fast-equals": { "version": "5.4.0", "resolved": "https://registry.npmjs.org/fast-equals/-/fast-equals-5.4.0.tgz", "integrity": "sha512-jt2DW/aNFNwke7AUd+Z+e6pz39KO5rzdbbFCg2sGafS4mk13MI7Z8O5z9cADNn5lhGODIgLwug6TZO2ctf7kcw==", - "license": "MIT", "engines": { "node": ">=6.0.0" } @@ -5438,22 +5503,19 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/fast-levenshtein": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", - "dev": true, - "license": "MIT" + "dev": true }, "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" }, @@ -5470,15 +5532,13 @@ "version": "0.8.2", "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.8.2.tgz", "integrity": "sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/file-entry-cache": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", "dev": true, - "license": "MIT", "dependencies": { "flat-cache": "^4.0.0" }, @@ -5491,7 +5551,6 @@ "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" @@ -5508,7 +5567,6 @@ "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", "dev": true, - "license": "MIT", "dependencies": { "flatted": "^3.2.9", "keyv": "^4.5.4" @@ -5518,23 +5576,21 @@ } }, "node_modules/flatted": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", - "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", - "dev": true, - "license": "ISC" + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz", + "integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==", + "dev": true }, "node_modules/follow-redirects": { - "version": "1.15.11", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz", - "integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==", + "version": "1.16.0", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.16.0.tgz", + "integrity": "sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw==", "funding": [ { "type": "individual", "url": "https://github.com/sponsors/RubenVerborgh" } ], - "license": "MIT", "engines": { "node": ">=4.0" }, @@ -5549,7 +5605,6 @@ "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" @@ -5565,7 +5620,6 @@ "version": "4.0.5", "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", - "license": "MIT", "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", @@ -5583,11 +5637,11 @@ "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", "dev": true, "hasInstallScript": true, - "license": "MIT", "optional": true, "os": [ "darwin" ], + "peer": true, "engines": { "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } @@ -5596,16 +5650,23 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", - "license": "MIT", "funding": { "url": "https://github.com/sponsors/ljharb" } }, + "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, + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/get-intrinsic": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", - "license": "MIT", "dependencies": { "call-bind-apply-helpers": "^1.0.2", "es-define-property": "^1.0.1", @@ -5629,7 +5690,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/get-nonce/-/get-nonce-1.0.1.tgz", "integrity": "sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==", - "license": "MIT", "engines": { "node": ">=6" } @@ -5638,7 +5698,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", - "license": "MIT", "dependencies": { "dunder-proto": "^1.0.1", "es-object-atoms": "^1.0.0" @@ -5650,8 +5709,7 @@ "node_modules/github-slugger": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/github-slugger/-/github-slugger-2.0.0.tgz", - "integrity": "sha512-IaOQ9puYtjrkq7Y0Ygl9KDZnrf/aiUJYUpVf89y8kyaxbRG7Y1SrX/jaumrv81vc61+kiMempujsM3Yw7w5qcw==", - "license": "ISC" + "integrity": "sha512-IaOQ9puYtjrkq7Y0Ygl9KDZnrf/aiUJYUpVf89y8kyaxbRG7Y1SrX/jaumrv81vc61+kiMempujsM3Yw7w5qcw==" }, "node_modules/glob": { "version": "10.5.0", @@ -5659,7 +5717,6 @@ "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", @@ -5680,7 +5737,6 @@ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", "dev": true, - "license": "ISC", "dependencies": { "is-glob": "^4.0.3" }, @@ -5689,23 +5745,21 @@ } }, "node_modules/glob/node_modules/brace-expansion": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", - "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "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/glob/node_modules/minimatch": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "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.1" + "brace-expansion": "^2.0.2" }, "engines": { "node": ">=16 || 14 >=14.17" @@ -5719,7 +5773,6 @@ "resolved": "https://registry.npmjs.org/globals/-/globals-16.5.0.tgz", "integrity": "sha512-c/c15i26VrJ4IRt5Z89DnIzCGDn9EcebibhAOjw5ibqEHsE1wLUgkPn9RDmNcUKyU87GeaL633nyJ+pplFR2ZQ==", "dev": true, - "license": "MIT", "engines": { "node": ">=18" }, @@ -5731,7 +5784,6 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", - "license": "MIT", "engines": { "node": ">= 0.4" }, @@ -5744,7 +5796,6 @@ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true, - "license": "MIT", "engines": { "node": ">=8" } @@ -5753,7 +5804,6 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", - "license": "MIT", "engines": { "node": ">= 0.4" }, @@ -5765,7 +5815,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", - "license": "MIT", "dependencies": { "has-symbols": "^1.0.3" }, @@ -5777,10 +5826,9 @@ } }, "node_modules/hasown": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", - "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", - "license": "MIT", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.3.tgz", + "integrity": "sha512-ej4AhfhfL2Q2zpMmLo7U1Uv9+PyhIZpgQLGT1F9miIGmiCJIoCgSmczFdrc97mWT4kVY72KA+WnnhJ5pghSvSg==", "dependencies": { "function-bind": "^1.1.2" }, @@ -5792,7 +5840,6 @@ "version": "2.0.3", "resolved": "https://registry.npmjs.org/hast-util-from-html/-/hast-util-from-html-2.0.3.tgz", "integrity": "sha512-CUSRHXyKjzHov8yKsQjGOElXy/3EKpyX56ELnkHH34vDVw1N1XSQ1ZcAvTyAPtGqLTuKP/uxM+aLkSPqF/EtMw==", - "license": "MIT", "dependencies": { "@types/hast": "^3.0.0", "devlop": "^1.1.0", @@ -5810,7 +5857,6 @@ "version": "8.0.3", "resolved": "https://registry.npmjs.org/hast-util-from-parse5/-/hast-util-from-parse5-8.0.3.tgz", "integrity": "sha512-3kxEVkEKt0zvcZ3hCRYI8rqrgwtlIOFMWkbclACvjlDw8Li9S2hk/d51OI0nr/gIpdMHNepwgOKqZ/sy0Clpyg==", - "license": "MIT", "dependencies": { "@types/hast": "^3.0.0", "@types/unist": "^3.0.0", @@ -5830,7 +5876,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/hast-util-has-property/-/hast-util-has-property-3.0.0.tgz", "integrity": "sha512-MNilsvEKLFpV604hwfhVStK0usFY/QmM5zX16bo7EjnAEGofr5YyI37kzopBlZJkHD4t887i+q/C8/tr5Q94cA==", - "license": "MIT", "dependencies": { "@types/hast": "^3.0.0" }, @@ -5843,7 +5888,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/hast-util-heading-rank/-/hast-util-heading-rank-3.0.0.tgz", "integrity": "sha512-EJKb8oMUXVHcWZTDepnr+WNbfnXKFNf9duMesmr4S8SXTJBJ9M4Yok08pu9vxdJwdlGRhVumk9mEhkEvKGifwA==", - "license": "MIT", "dependencies": { "@types/hast": "^3.0.0" }, @@ -5856,7 +5900,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/hast-util-is-element/-/hast-util-is-element-3.0.0.tgz", "integrity": "sha512-Val9mnv2IWpLbNPqc/pUem+a7Ipj2aHacCwgNfTiK0vJKl0LF+4Ba4+v1oPHFpf3bLYmreq0/l3Gud9S5OH42g==", - "license": "MIT", "dependencies": { "@types/hast": "^3.0.0" }, @@ -5869,7 +5912,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/hast-util-parse-selector/-/hast-util-parse-selector-4.0.0.tgz", "integrity": "sha512-wkQCkSYoOGCRKERFWcxMVMOcYE2K1AaNLU8DXS9arxnLOUEWbOXKXiJUNzEpqZ3JOKpnha3jkFrumEjVliDe7A==", - "license": "MIT", "dependencies": { "@types/hast": "^3.0.0" }, @@ -5882,7 +5924,6 @@ "version": "9.1.0", "resolved": "https://registry.npmjs.org/hast-util-raw/-/hast-util-raw-9.1.0.tgz", "integrity": "sha512-Y8/SBAHkZGoNkpzqqfCldijcuUKh7/su31kEBp67cFY09Wy0mTRgtsLYsiIxMJxlu0f6AA5SUTbDR8K0rxnbUw==", - "license": "MIT", "dependencies": { "@types/hast": "^3.0.0", "@types/unist": "^3.0.0", @@ -5907,7 +5948,6 @@ "version": "6.0.4", "resolved": "https://registry.npmjs.org/hast-util-select/-/hast-util-select-6.0.4.tgz", "integrity": "sha512-RqGS1ZgI0MwxLaKLDxjprynNzINEkRHY2i8ln4DDjgv9ZhcYVIHN9rlpiYsqtFwrgpYU361SyWDQcGNIBVu3lw==", - "license": "MIT", "dependencies": { "@types/hast": "^3.0.0", "@types/unist": "^3.0.0", @@ -5934,7 +5974,6 @@ "version": "9.0.5", "resolved": "https://registry.npmjs.org/hast-util-to-html/-/hast-util-to-html-9.0.5.tgz", "integrity": "sha512-OguPdidb+fbHQSU4Q4ZiLKnzWo8Wwsf5bZfbvu7//a9oTYoqD/fWpe96NuHkoS9h0ccGOTe0C4NGXdtS0iObOw==", - "license": "MIT", "dependencies": { "@types/hast": "^3.0.0", "@types/unist": "^3.0.0", @@ -5957,7 +5996,6 @@ "version": "2.3.6", "resolved": "https://registry.npmjs.org/hast-util-to-jsx-runtime/-/hast-util-to-jsx-runtime-2.3.6.tgz", "integrity": "sha512-zl6s8LwNyo1P9uw+XJGvZtdFF1GdAkOg8ujOw+4Pyb76874fLps4ueHXDhXWdk6YHQ6OgUtinliG7RsYvCbbBg==", - "license": "MIT", "dependencies": { "@types/estree": "^1.0.0", "@types/hast": "^3.0.0", @@ -5984,7 +6022,6 @@ "version": "8.0.1", "resolved": "https://registry.npmjs.org/hast-util-to-parse5/-/hast-util-to-parse5-8.0.1.tgz", "integrity": "sha512-MlWT6Pjt4CG9lFCjiz4BH7l9wmrMkfkJYCxFwKQic8+RTZgWPuWxwAfjJElsXkex7DJjfSJsQIt931ilUgmwdA==", - "license": "MIT", "dependencies": { "@types/hast": "^3.0.0", "comma-separated-tokens": "^2.0.0", @@ -6003,7 +6040,6 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/hast-util-to-string/-/hast-util-to-string-3.0.1.tgz", "integrity": "sha512-XelQVTDWvqcl3axRfI0xSeoVKzyIFPwsAGSLIsKdJKQMXDYJS4WYrBNF/8J7RdhIcFI2BOHgAifggsvsxp/3+A==", - "license": "MIT", "dependencies": { "@types/hast": "^3.0.0" }, @@ -6016,7 +6052,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/hast-util-whitespace/-/hast-util-whitespace-3.0.0.tgz", "integrity": "sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==", - "license": "MIT", "dependencies": { "@types/hast": "^3.0.0" }, @@ -6029,7 +6064,6 @@ "version": "9.0.1", "resolved": "https://registry.npmjs.org/hastscript/-/hastscript-9.0.1.tgz", "integrity": "sha512-g7df9rMFX/SPi34tyGCyUBREQoKkapwdY/T04Qn9TDWfHhAYt4/I0gMVirzK5wEzeUqIjEB+LXC/ypb7Aqno5w==", - "license": "MIT", "dependencies": { "@types/hast": "^3.0.0", "comma-separated-tokens": "^2.0.0", @@ -6047,7 +6081,6 @@ "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-4.0.0.tgz", "integrity": "sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==", "dev": true, - "license": "MIT", "dependencies": { "whatwg-encoding": "^3.1.1" }, @@ -6059,14 +6092,12 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/html-url-attributes": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/html-url-attributes/-/html-url-attributes-3.0.1.tgz", "integrity": "sha512-ol6UPyBWqsrO6EJySPz2O7ZSr856WDrEzM5zMqp+FJJLGMW35cLYmmZnl0vztAZxRUoNZJFTCohfjuIJ8I4QBQ==", - "license": "MIT", "funding": { "type": "opencollective", "url": "https://opencollective.com/unified" @@ -6076,7 +6107,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/html-void-elements/-/html-void-elements-3.0.0.tgz", "integrity": "sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==", - "license": "MIT", "funding": { "type": "github", "url": "https://github.com/sponsors/wooorm" @@ -6087,7 +6117,6 @@ "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", "dev": true, - "license": "MIT", "dependencies": { "agent-base": "^7.1.0", "debug": "^4.3.4" @@ -6101,7 +6130,6 @@ "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", "dev": true, - "license": "MIT", "dependencies": { "agent-base": "^7.1.2", "debug": "4" @@ -6115,7 +6143,6 @@ "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", "dev": true, - "license": "MIT", "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" }, @@ -6128,7 +6155,6 @@ "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", "dev": true, - "license": "MIT", "engines": { "node": ">= 4" } @@ -6138,7 +6164,6 @@ "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", "dev": true, - "license": "MIT", "dependencies": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" @@ -6155,7 +6180,6 @@ "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", "dev": true, - "license": "MIT", "engines": { "node": ">=0.8.19" } @@ -6165,7 +6189,6 @@ "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", "dev": true, - "license": "MIT", "engines": { "node": ">=8" } @@ -6173,14 +6196,12 @@ "node_modules/inline-style-parser": { "version": "0.2.7", "resolved": "https://registry.npmjs.org/inline-style-parser/-/inline-style-parser-0.2.7.tgz", - "integrity": "sha512-Nb2ctOyNR8DqQoR0OwRG95uNWIC0C1lCgf5Naz5H6Ji72KZ8OcFZLz2P5sNgwlyoJ8Yif11oMuYs5pBQa86csA==", - "license": "MIT" + "integrity": "sha512-Nb2ctOyNR8DqQoR0OwRG95uNWIC0C1lCgf5Naz5H6Ji72KZ8OcFZLz2P5sNgwlyoJ8Yif11oMuYs5pBQa86csA==" }, "node_modules/is-alphabetical": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-2.0.1.tgz", "integrity": "sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ==", - "license": "MIT", "funding": { "type": "github", "url": "https://github.com/sponsors/wooorm" @@ -6190,7 +6211,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/is-alphanumerical/-/is-alphanumerical-2.0.1.tgz", "integrity": "sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw==", - "license": "MIT", "dependencies": { "is-alphabetical": "^2.0.0", "is-decimal": "^2.0.0" @@ -6204,7 +6224,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-2.0.1.tgz", "integrity": "sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A==", - "license": "MIT", "funding": { "type": "github", "url": "https://github.com/sponsors/wooorm" @@ -6215,7 +6234,6 @@ "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" } @@ -6225,7 +6243,6 @@ "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" } @@ -6235,7 +6252,6 @@ "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" }, @@ -6247,7 +6263,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/is-hexadecimal/-/is-hexadecimal-2.0.1.tgz", "integrity": "sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg==", - "license": "MIT", "funding": { "type": "github", "url": "https://github.com/sponsors/wooorm" @@ -6257,7 +6272,6 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz", "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==", - "license": "MIT", "engines": { "node": ">=12" }, @@ -6269,22 +6283,19 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==", - "dev": true, - "license": "MIT" + "dev": true }, "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" + "dev": true }, "node_modules/istanbul-lib-coverage": { "version": "3.2.2", "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", "dev": true, - "license": "BSD-3-Clause", "engines": { "node": ">=8" } @@ -6294,7 +6305,6 @@ "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", "dev": true, - "license": "BSD-3-Clause", "dependencies": { "istanbul-lib-coverage": "^3.0.0", "make-dir": "^4.0.0", @@ -6309,7 +6319,6 @@ "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-5.0.6.tgz", "integrity": "sha512-yg2d+Em4KizZC5niWhQaIomgf5WlL4vOOjZ5xGCmF8SnPE/mDWWXgvRExdcpCgh9lLRRa1/fSYp2ymmbJ1pI+A==", "dev": true, - "license": "BSD-3-Clause", "dependencies": { "@jridgewell/trace-mapping": "^0.3.23", "debug": "^4.1.1", @@ -6324,7 +6333,6 @@ "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.2.0.tgz", "integrity": "sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==", "dev": true, - "license": "BSD-3-Clause", "dependencies": { "html-escaper": "^2.0.0", "istanbul-lib-report": "^3.0.0" @@ -6338,7 +6346,6 @@ "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" }, @@ -6353,16 +6360,13 @@ "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", - "peer": true + "dev": true }, "node_modules/js-yaml": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", "dev": true, - "license": "MIT", "dependencies": { "argparse": "^2.0.1" }, @@ -6375,7 +6379,6 @@ "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-25.0.1.tgz", "integrity": "sha512-8i7LzZj7BF8uplX+ZyOlIz86V6TAsSs+np6m1kpW9u0JWi4z/1t+FzcK1aek+ybTnAC4KhBL4uXCNT0wcUIeCw==", "dev": true, - "license": "MIT", "dependencies": { "cssstyle": "^4.1.0", "data-urls": "^5.0.0", @@ -6411,33 +6414,53 @@ } } }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "dev": true, + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/json-buffer": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/json-stable-stringify-without-jsonify": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true + }, + "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/keyv": { "version": "4.5.4", "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", "dev": true, - "license": "MIT", "dependencies": { "json-buffer": "3.0.1" } @@ -6447,7 +6470,6 @@ "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", "dev": true, - "license": "MIT", "dependencies": { "prelude-ls": "^1.2.1", "type-check": "~0.4.0" @@ -6456,27 +6478,16 @@ "node": ">= 0.8.0" } }, - "node_modules/linkify-it": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-5.0.0.tgz", - "integrity": "sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==", - "license": "MIT", - "dependencies": { - "uc.micro": "^2.0.0" - } - }, "node_modules/linkifyjs": { "version": "4.3.2", "resolved": "https://registry.npmjs.org/linkifyjs/-/linkifyjs-4.3.2.tgz", - "integrity": "sha512-NT1CJtq3hHIreOianA8aSXn6Cw0JzYOuDQbOrSPe7gqFnCpKP++MQe3ODgO3oh2GJFORkAAdqredOa60z63GbA==", - "license": "MIT" + "integrity": "sha512-NT1CJtq3hHIreOianA8aSXn6Cw0JzYOuDQbOrSPe7gqFnCpKP++MQe3ODgO3oh2GJFORkAAdqredOa60z63GbA==" }, "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": "MIT", "dependencies": { "p-locate": "^5.0.0" }, @@ -6491,14 +6502,12 @@ "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/longest-streak": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/longest-streak/-/longest-streak-3.1.0.tgz", "integrity": "sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==", - "license": "MIT", "funding": { "type": "github", "url": "https://github.com/sponsors/wooorm" @@ -6508,21 +6517,21 @@ "version": "3.2.1", "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.2.1.tgz", "integrity": "sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==", - "dev": true, - "license": "MIT" + "dev": true }, "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==", + "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/lucide-react": { "version": "0.562.0", "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.562.0.tgz", "integrity": "sha512-82hOAu7y0dbVuFfmO4bYF1XEwYk/mEbM5E+b1jgci/udUBEE/R7LF5Ip0CCEmXe8AybRM8L+04eP+LGZeDvkiw==", - "license": "ISC", "peerDependencies": { "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" } @@ -6532,7 +6541,6 @@ "resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.5.0.tgz", "integrity": "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==", "dev": true, - "license": "MIT", "peer": true, "bin": { "lz-string": "bin/bin.js" @@ -6543,7 +6551,6 @@ "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" } @@ -6553,7 +6560,6 @@ "resolved": "https://registry.npmjs.org/magicast/-/magicast-0.3.5.tgz", "integrity": "sha512-L0WhttDl+2BOsybvEOLK7fW3UA0OQ0IQ2d6Zl2x/a6vVRs3bAY0ECOSHHeL5jD+SbOpOCUEi0y1DgHEn9Qn1AQ==", "dev": true, - "license": "MIT", "dependencies": { "@babel/parser": "^7.25.4", "@babel/types": "^7.25.4", @@ -6565,7 +6571,6 @@ "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", "dev": true, - "license": "MIT", "dependencies": { "semver": "^7.5.3" }, @@ -6576,40 +6581,22 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/markdown-it": { - "version": "14.1.1", - "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-14.1.1.tgz", - "integrity": "sha512-BuU2qnTti9YKgK5N+IeMubp14ZUKUUw7yeJbkjtosvHiP0AZ5c8IAgEMk79D0eC8F23r4Ac/q8cAIFdm2FtyoA==", - "license": "MIT", - "dependencies": { - "argparse": "^2.0.1", - "entities": "^4.4.0", - "linkify-it": "^5.0.0", - "mdurl": "^2.0.0", - "punycode.js": "^2.3.1", - "uc.micro": "^2.1.0" - }, + "node_modules/make-dir/node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "dev": true, "bin": { - "markdown-it": "bin/markdown-it.mjs" - } - }, - "node_modules/markdown-it/node_modules/entities": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", - "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", - "license": "BSD-2-Clause", - "engines": { - "node": ">=0.12" + "semver": "bin/semver.js" }, - "funding": { - "url": "https://github.com/fb55/entities?sponsor=1" + "engines": { + "node": ">=10" } }, "node_modules/markdown-table": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/markdown-table/-/markdown-table-3.0.4.tgz", "integrity": "sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw==", - "license": "MIT", "funding": { "type": "github", "url": "https://github.com/sponsors/wooorm" @@ -6619,7 +6606,6 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", - "license": "MIT", "engines": { "node": ">= 0.4" } @@ -6628,7 +6614,6 @@ "version": "3.0.2", "resolved": "https://registry.npmjs.org/mdast-util-find-and-replace/-/mdast-util-find-and-replace-3.0.2.tgz", "integrity": "sha512-Tmd1Vg/m3Xz43afeNxDIhWRtFZgM2VLyaf4vSTYwudTyeuTneoL3qtWMA5jeLyz/O1vDJmmV4QuScFCA2tBPwg==", - "license": "MIT", "dependencies": { "@types/mdast": "^4.0.0", "escape-string-regexp": "^5.0.0", @@ -6644,7 +6629,6 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==", - "license": "MIT", "engines": { "node": ">=12" }, @@ -6653,10 +6637,9 @@ } }, "node_modules/mdast-util-from-markdown": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-2.0.2.tgz", - "integrity": "sha512-uZhTV/8NBuw0WHkPTrCqDOl0zVe1BIng5ZtHoDk49ME1qqcjYmmLmOf0gELgcRMxN4w2iuIeVso5/6QymSrgmA==", - "license": "MIT", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-2.0.3.tgz", + "integrity": "sha512-W4mAWTvSlKvf8L6J+VN9yLSqQ9AOAAvHuoDAmPkz4dHf553m5gVj2ejadHJhoJmcmxEnOv6Pa8XJhpxE93kb8Q==", "dependencies": { "@types/mdast": "^4.0.0", "@types/unist": "^3.0.0", @@ -6680,7 +6663,6 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/mdast-util-gfm/-/mdast-util-gfm-3.1.0.tgz", "integrity": "sha512-0ulfdQOM3ysHhCJ1p06l0b0VKlhU0wuQs3thxZQagjcjPrlFRqY215uZGHHJan9GEAXd9MbfPjFJz+qMkVR6zQ==", - "license": "MIT", "dependencies": { "mdast-util-from-markdown": "^2.0.0", "mdast-util-gfm-autolink-literal": "^2.0.0", @@ -6699,7 +6681,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/mdast-util-gfm-autolink-literal/-/mdast-util-gfm-autolink-literal-2.0.1.tgz", "integrity": "sha512-5HVP2MKaP6L+G6YaxPNjuL0BPrq9orG3TsrZ9YXbA3vDw/ACI4MEsnoDpn6ZNm7GnZgtAcONJyPhOP8tNJQavQ==", - "license": "MIT", "dependencies": { "@types/mdast": "^4.0.0", "ccount": "^2.0.0", @@ -6716,7 +6697,6 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/mdast-util-gfm-footnote/-/mdast-util-gfm-footnote-2.1.0.tgz", "integrity": "sha512-sqpDWlsHn7Ac9GNZQMeUzPQSMzR6Wv0WKRNvQRg0KqHh02fpTz69Qc1QSseNX29bhz1ROIyNyxExfawVKTm1GQ==", - "license": "MIT", "dependencies": { "@types/mdast": "^4.0.0", "devlop": "^1.1.0", @@ -6733,7 +6713,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/mdast-util-gfm-strikethrough/-/mdast-util-gfm-strikethrough-2.0.0.tgz", "integrity": "sha512-mKKb915TF+OC5ptj5bJ7WFRPdYtuHv0yTRxK2tJvi+BDqbkiG7h7u/9SI89nRAYcmap2xHQL9D+QG/6wSrTtXg==", - "license": "MIT", "dependencies": { "@types/mdast": "^4.0.0", "mdast-util-from-markdown": "^2.0.0", @@ -6748,7 +6727,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/mdast-util-gfm-table/-/mdast-util-gfm-table-2.0.0.tgz", "integrity": "sha512-78UEvebzz/rJIxLvE7ZtDd/vIQ0RHv+3Mh5DR96p7cS7HsBhYIICDBCu8csTNWNO6tBWfqXPWekRuj2FNOGOZg==", - "license": "MIT", "dependencies": { "@types/mdast": "^4.0.0", "devlop": "^1.0.0", @@ -6765,7 +6743,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/mdast-util-gfm-task-list-item/-/mdast-util-gfm-task-list-item-2.0.0.tgz", "integrity": "sha512-IrtvNvjxC1o06taBAVJznEnkiHxLFTzgonUdy8hzFVeDun0uTjxxrRGVaNFqkU1wJR3RBPEfsxmU6jDWPofrTQ==", - "license": "MIT", "dependencies": { "@types/mdast": "^4.0.0", "devlop": "^1.0.0", @@ -6781,7 +6758,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/mdast-util-mdx-expression/-/mdast-util-mdx-expression-2.0.1.tgz", "integrity": "sha512-J6f+9hUp+ldTZqKRSg7Vw5V6MqjATc+3E4gf3CFNcuZNWD8XdyI6zQ8GqH7f8169MM6P7hMBRDVGnn7oHB9kXQ==", - "license": "MIT", "dependencies": { "@types/estree-jsx": "^1.0.0", "@types/hast": "^3.0.0", @@ -6799,7 +6775,6 @@ "version": "3.2.0", "resolved": "https://registry.npmjs.org/mdast-util-mdx-jsx/-/mdast-util-mdx-jsx-3.2.0.tgz", "integrity": "sha512-lj/z8v0r6ZtsN/cGNNtemmmfoLAFZnjMbNyLzBafjzikOM+glrjNHPlf6lQDOTccj9n5b0PPihEBbhneMyGs1Q==", - "license": "MIT", "dependencies": { "@types/estree-jsx": "^1.0.0", "@types/hast": "^3.0.0", @@ -6823,7 +6798,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/mdast-util-mdxjs-esm/-/mdast-util-mdxjs-esm-2.0.1.tgz", "integrity": "sha512-EcmOpxsZ96CvlP03NghtH1EsLtr0n9Tm4lPUJUBccV9RwUOneqSycg19n5HGzCf+10LozMRSObtVr3ee1WoHtg==", - "license": "MIT", "dependencies": { "@types/estree-jsx": "^1.0.0", "@types/hast": "^3.0.0", @@ -6841,7 +6815,6 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/mdast-util-phrasing/-/mdast-util-phrasing-4.1.0.tgz", "integrity": "sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w==", - "license": "MIT", "dependencies": { "@types/mdast": "^4.0.0", "unist-util-is": "^6.0.0" @@ -6855,7 +6828,6 @@ "version": "13.2.1", "resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-13.2.1.tgz", "integrity": "sha512-cctsq2wp5vTsLIcaymblUriiTcZd0CwWtCbLvrOzYCDZoWyMNV8sZ7krj09FSnsiJi3WVsHLM4k6Dq/yaPyCXA==", - "license": "MIT", "dependencies": { "@types/hast": "^3.0.0", "@types/mdast": "^4.0.0", @@ -6876,7 +6848,6 @@ "version": "2.1.2", "resolved": "https://registry.npmjs.org/mdast-util-to-markdown/-/mdast-util-to-markdown-2.1.2.tgz", "integrity": "sha512-xj68wMTvGXVOKonmog6LwyJKrYXZPvlwabaryTjLh9LuvovB/KAH+kvi8Gjj+7rJjsFi23nkUxRQv1KqSroMqA==", - "license": "MIT", "dependencies": { "@types/mdast": "^4.0.0", "@types/unist": "^3.0.0", @@ -6897,7 +6868,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-4.0.0.tgz", "integrity": "sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==", - "license": "MIT", "dependencies": { "@types/mdast": "^4.0.0" }, @@ -6906,12 +6876,6 @@ "url": "https://opencollective.com/unified" } }, - "node_modules/mdurl": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-2.0.0.tgz", - "integrity": "sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==", - "license": "MIT" - }, "node_modules/micromark": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/micromark/-/micromark-4.0.2.tgz", @@ -6926,7 +6890,6 @@ "url": "https://opencollective.com/unified" } ], - "license": "MIT", "dependencies": { "@types/debug": "^4.0.0", "debug": "^4.0.0", @@ -6961,7 +6924,6 @@ "url": "https://opencollective.com/unified" } ], - "license": "MIT", "dependencies": { "decode-named-character-reference": "^1.0.0", "devlop": "^1.0.0", @@ -6985,7 +6947,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/micromark-extension-gfm/-/micromark-extension-gfm-3.0.0.tgz", "integrity": "sha512-vsKArQsicm7t0z2GugkCKtZehqUm31oeGBV/KVSorWSy8ZlNAv7ytjFhvaryUiCUJYqs+NoE6AFhpQvBTM6Q4w==", - "license": "MIT", "dependencies": { "micromark-extension-gfm-autolink-literal": "^2.0.0", "micromark-extension-gfm-footnote": "^2.0.0", @@ -7005,7 +6966,6 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/micromark-extension-gfm-autolink-literal/-/micromark-extension-gfm-autolink-literal-2.1.0.tgz", "integrity": "sha512-oOg7knzhicgQ3t4QCjCWgTmfNhvQbDDnJeVu9v81r7NltNCVmhPy1fJRX27pISafdjL+SVc4d3l48Gb6pbRypw==", - "license": "MIT", "dependencies": { "micromark-util-character": "^2.0.0", "micromark-util-sanitize-uri": "^2.0.0", @@ -7021,7 +6981,6 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/micromark-extension-gfm-footnote/-/micromark-extension-gfm-footnote-2.1.0.tgz", "integrity": "sha512-/yPhxI1ntnDNsiHtzLKYnE3vf9JZ6cAisqVDauhp4CEHxlb4uoOTxOCJ+9s51bIB8U1N1FJ1RXOKTIlD5B/gqw==", - "license": "MIT", "dependencies": { "devlop": "^1.0.0", "micromark-core-commonmark": "^2.0.0", @@ -7041,7 +7000,6 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/micromark-extension-gfm-strikethrough/-/micromark-extension-gfm-strikethrough-2.1.0.tgz", "integrity": "sha512-ADVjpOOkjz1hhkZLlBiYA9cR2Anf8F4HqZUO6e5eDcPQd0Txw5fxLzzxnEkSkfnD0wziSGiv7sYhk/ktvbf1uw==", - "license": "MIT", "dependencies": { "devlop": "^1.0.0", "micromark-util-chunked": "^2.0.0", @@ -7059,7 +7017,6 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/micromark-extension-gfm-table/-/micromark-extension-gfm-table-2.1.1.tgz", "integrity": "sha512-t2OU/dXXioARrC6yWfJ4hqB7rct14e8f7m0cbI5hUmDyyIlwv5vEtooptH8INkbLzOatzKuVbQmAYcbWoyz6Dg==", - "license": "MIT", "dependencies": { "devlop": "^1.0.0", "micromark-factory-space": "^2.0.0", @@ -7076,7 +7033,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/micromark-extension-gfm-tagfilter/-/micromark-extension-gfm-tagfilter-2.0.0.tgz", "integrity": "sha512-xHlTOmuCSotIA8TW1mDIM6X2O1SiX5P9IuDtqGonFhEK0qgRI4yeC6vMxEV2dgyr2TiD+2PQ10o+cOhdVAcwfg==", - "license": "MIT", "dependencies": { "micromark-util-types": "^2.0.0" }, @@ -7089,7 +7045,6 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/micromark-extension-gfm-task-list-item/-/micromark-extension-gfm-task-list-item-2.1.0.tgz", "integrity": "sha512-qIBZhqxqI6fjLDYFTBIa4eivDMnP+OZqsNwmQ3xNLE4Cxwc+zfQEfbs6tzAo2Hjq+bh6q5F+Z8/cksrLFYWQQw==", - "license": "MIT", "dependencies": { "devlop": "^1.0.0", "micromark-factory-space": "^2.0.0", @@ -7116,7 +7071,6 @@ "url": "https://opencollective.com/unified" } ], - "license": "MIT", "dependencies": { "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", @@ -7137,7 +7091,6 @@ "url": "https://opencollective.com/unified" } ], - "license": "MIT", "dependencies": { "devlop": "^1.0.0", "micromark-util-character": "^2.0.0", @@ -7159,7 +7112,6 @@ "url": "https://opencollective.com/unified" } ], - "license": "MIT", "dependencies": { "micromark-util-character": "^2.0.0", "micromark-util-types": "^2.0.0" @@ -7179,7 +7131,6 @@ "url": "https://opencollective.com/unified" } ], - "license": "MIT", "dependencies": { "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", @@ -7201,7 +7152,6 @@ "url": "https://opencollective.com/unified" } ], - "license": "MIT", "dependencies": { "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", @@ -7223,7 +7173,6 @@ "url": "https://opencollective.com/unified" } ], - "license": "MIT", "dependencies": { "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" @@ -7243,7 +7192,6 @@ "url": "https://opencollective.com/unified" } ], - "license": "MIT", "dependencies": { "micromark-util-symbol": "^2.0.0" } @@ -7262,7 +7210,6 @@ "url": "https://opencollective.com/unified" } ], - "license": "MIT", "dependencies": { "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", @@ -7283,7 +7230,6 @@ "url": "https://opencollective.com/unified" } ], - "license": "MIT", "dependencies": { "micromark-util-chunked": "^2.0.0", "micromark-util-types": "^2.0.0" @@ -7303,7 +7249,6 @@ "url": "https://opencollective.com/unified" } ], - "license": "MIT", "dependencies": { "micromark-util-symbol": "^2.0.0" } @@ -7322,7 +7267,6 @@ "url": "https://opencollective.com/unified" } ], - "license": "MIT", "dependencies": { "decode-named-character-reference": "^1.0.0", "micromark-util-character": "^2.0.0", @@ -7343,8 +7287,7 @@ "type": "OpenCollective", "url": "https://opencollective.com/unified" } - ], - "license": "MIT" + ] }, "node_modules/micromark-util-html-tag-name": { "version": "2.0.1", @@ -7359,8 +7302,7 @@ "type": "OpenCollective", "url": "https://opencollective.com/unified" } - ], - "license": "MIT" + ] }, "node_modules/micromark-util-normalize-identifier": { "version": "2.0.1", @@ -7376,7 +7318,6 @@ "url": "https://opencollective.com/unified" } ], - "license": "MIT", "dependencies": { "micromark-util-symbol": "^2.0.0" } @@ -7395,7 +7336,6 @@ "url": "https://opencollective.com/unified" } ], - "license": "MIT", "dependencies": { "micromark-util-types": "^2.0.0" } @@ -7414,7 +7354,6 @@ "url": "https://opencollective.com/unified" } ], - "license": "MIT", "dependencies": { "micromark-util-character": "^2.0.0", "micromark-util-encode": "^2.0.0", @@ -7435,7 +7374,6 @@ "url": "https://opencollective.com/unified" } ], - "license": "MIT", "dependencies": { "devlop": "^1.0.0", "micromark-util-chunked": "^2.0.0", @@ -7456,8 +7394,7 @@ "type": "OpenCollective", "url": "https://opencollective.com/unified" } - ], - "license": "MIT" + ] }, "node_modules/micromark-util-types": { "version": "2.0.2", @@ -7472,14 +7409,12 @@ "type": "OpenCollective", "url": "https://opencollective.com/unified" } - ], - "license": "MIT" + ] }, "node_modules/mime-db": { "version": "1.52.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "license": "MIT", "engines": { "node": ">= 0.6" } @@ -7488,7 +7423,6 @@ "version": "2.1.35", "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "license": "MIT", "dependencies": { "mime-db": "1.52.0" }, @@ -7501,17 +7435,15 @@ "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz", "integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==", "dev": true, - "license": "MIT", "engines": { "node": ">=4" } }, "node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", "dev": true, - "license": "ISC", "dependencies": { "brace-expansion": "^1.1.7" }, @@ -7520,11 +7452,10 @@ } }, "node_modules/minipass": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", - "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", + "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", "dev": true, - "license": "ISC", "engines": { "node": ">=16 || 14 >=14.17" } @@ -7534,7 +7465,6 @@ "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-2.0.1.tgz", "integrity": "sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==", "dev": true, - "license": "MIT", "engines": { "node": ">=10" } @@ -7542,20 +7472,19 @@ "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==", - "license": "MIT" + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" }, "node_modules/nanoid": { "version": "3.3.11", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "dev": true, "funding": [ { "type": "github", "url": "https://github.com/sponsors/ai" } ], - "license": "MIT", "bin": { "nanoid": "bin/nanoid.cjs" }, @@ -7567,14 +7496,18 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", - "dev": true, - "license": "MIT" + "dev": true + }, + "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 }, "node_modules/nth-check": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", - "license": "BSD-2-Clause", "dependencies": { "boolbase": "^1.0.0" }, @@ -7586,15 +7519,13 @@ "version": "2.2.23", "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.23.tgz", "integrity": "sha512-7wfH4sLbt4M0gCDzGE6vzQBo0bfTKjU7Sfpqy/7gs1qBfYz2vEJH6vXcBKpO3+6Yu1telwd0t9HpyOoLEQQbIQ==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/optionator": { "version": "0.9.4", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", "dev": true, - "license": "MIT", "dependencies": { "deep-is": "^0.1.3", "fast-levenshtein": "^2.0.6", @@ -7610,15 +7541,13 @@ "node_modules/orderedmap": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/orderedmap/-/orderedmap-2.1.1.tgz", - "integrity": "sha512-TvAWxi0nDe1j/rtMcWcIj94+Ffe6n7zhow33h40SKxmsmozs6dz/e+EajymfoFcHd7sxNn8yHM8839uixMOV6g==", - "license": "MIT" + "integrity": "sha512-TvAWxi0nDe1j/rtMcWcIj94+Ffe6n7zhow33h40SKxmsmozs6dz/e+EajymfoFcHd7sxNn8yHM8839uixMOV6g==" }, "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" }, @@ -7634,7 +7563,6 @@ "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" }, @@ -7649,15 +7577,13 @@ "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" + "dev": true }, "node_modules/parent-module": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", "dev": true, - "license": "MIT", "dependencies": { "callsites": "^3.0.0" }, @@ -7669,7 +7595,6 @@ "version": "4.0.2", "resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-4.0.2.tgz", "integrity": "sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw==", - "license": "MIT", "dependencies": { "@types/unist": "^2.0.0", "character-entities-legacy": "^3.0.0", @@ -7687,20 +7612,17 @@ "node_modules/parse-entities/node_modules/@types/unist": { "version": "2.0.11", "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.11.tgz", - "integrity": "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==", - "license": "MIT" + "integrity": "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==" }, "node_modules/parse-numeric-range": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/parse-numeric-range/-/parse-numeric-range-1.3.0.tgz", - "integrity": "sha512-twN+njEipszzlMJd4ONUYgSfZPDxgHhT9Ahed5uTigpQn90FggW4SA/AIPq/6a149fTbE9qBEcSwE3FAEp6wQQ==", - "license": "ISC" + "integrity": "sha512-twN+njEipszzlMJd4ONUYgSfZPDxgHhT9Ahed5uTigpQn90FggW4SA/AIPq/6a149fTbE9qBEcSwE3FAEp6wQQ==" }, "node_modules/parse5": { "version": "7.3.0", "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz", "integrity": "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==", - "license": "MIT", "dependencies": { "entities": "^6.0.0" }, @@ -7713,7 +7635,6 @@ "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" } @@ -7723,7 +7644,6 @@ "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" } @@ -7733,7 +7653,6 @@ "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" @@ -7745,19 +7664,23 @@ "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 + }, "node_modules/pathe": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.2.tgz", "integrity": "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/pathval": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.1.tgz", "integrity": "sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==", "dev": true, - "license": "MIT", "engines": { "node": ">= 14.16" } @@ -7766,14 +7689,13 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", - "license": "ISC" + "dev": true }, "node_modules/picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "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" }, @@ -7782,9 +7704,10 @@ } }, "node_modules/postcss": { - "version": "8.4.49", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.49.tgz", - "integrity": "sha512-OCVPnIObs4N29kxTjzLfUryOkvZEq+pf8jTF0lg8E7uETuWHA+v7j3c/xJmiqpX450191LlmZfUKkXxkTry7nA==", + "version": "8.5.12", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.12.tgz", + "integrity": "sha512-W62t/Se6rA0Az3DfCL0AqJwXuKwBeYg6nOaIgzP+xZ7N5BFCI7DYi1qs6ygUYT6rvfi6t9k65UMLJC+PHZpDAA==", + "dev": true, "funding": [ { "type": "opencollective", @@ -7799,9 +7722,8 @@ "url": "https://github.com/sponsors/ai" } ], - "license": "MIT", "dependencies": { - "nanoid": "^3.3.7", + "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" }, @@ -7812,25 +7734,22 @@ "node_modules/postcss-value-parser": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", - "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", - "license": "MIT" + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==" }, "node_modules/prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", "dev": true, - "license": "MIT", "engines": { "node": ">= 0.8.0" } }, "node_modules/prettier": { - "version": "3.8.1", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.8.1.tgz", - "integrity": "sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg==", + "version": "3.8.3", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.8.3.tgz", + "integrity": "sha512-7igPTM53cGHMW8xWuVTydi2KO233VFiTNyF5hLJqpilHfmn8C8gPf+PS7dUT64YcXFbiMGZxS9pCSxL/Dxm/Jw==", "dev": true, - "license": "MIT", "bin": { "prettier": "bin/prettier.cjs" }, @@ -7846,7 +7765,6 @@ "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz", "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==", "dev": true, - "license": "MIT", "peer": true, "dependencies": { "ansi-regex": "^5.0.1", @@ -7862,7 +7780,6 @@ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", "dev": true, - "license": "MIT", "peer": true, "engines": { "node": ">=10" @@ -7875,35 +7792,23 @@ "version": "7.1.0", "resolved": "https://registry.npmjs.org/property-information/-/property-information-7.1.0.tgz", "integrity": "sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ==", - "license": "MIT", "funding": { "type": "github", "url": "https://github.com/sponsors/wooorm" } }, "node_modules/prosemirror-changeset": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/prosemirror-changeset/-/prosemirror-changeset-2.3.1.tgz", - "integrity": "sha512-j0kORIBm8ayJNl3zQvD1TTPHJX3g042et6y/KQhZhnPrruO8exkTgG8X+NRpj7kIyMMEx74Xb3DyMIBtO0IKkQ==", - "license": "MIT", + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/prosemirror-changeset/-/prosemirror-changeset-2.4.1.tgz", + "integrity": "sha512-96WBLhOaYhJ+kPhLg3uW359Tz6I/MfcrQfL4EGv4SrcqKEMC1gmoGrXHecPE8eOwTVCJ4IwgfzM8fFad25wNfw==", "dependencies": { "prosemirror-transform": "^1.0.0" } }, - "node_modules/prosemirror-collab": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/prosemirror-collab/-/prosemirror-collab-1.3.1.tgz", - "integrity": "sha512-4SnynYR9TTYaQVXd/ieUvsVV4PDMBzrq2xPUWutHivDuOshZXqQ5rGbZM84HEaXKbLdItse7weMGOUdDVcLKEQ==", - "license": "MIT", - "dependencies": { - "prosemirror-state": "^1.0.0" - } - }, "node_modules/prosemirror-commands": { "version": "1.7.1", "resolved": "https://registry.npmjs.org/prosemirror-commands/-/prosemirror-commands-1.7.1.tgz", "integrity": "sha512-rT7qZnQtx5c0/y/KlYaGvtG411S97UaL6gdp6RIZ23DLHanMYLyfGBV5DtSnZdthQql7W+lEVbpSfwtO8T+L2w==", - "license": "MIT", "dependencies": { "prosemirror-model": "^1.0.0", "prosemirror-state": "^1.0.0", @@ -7914,7 +7819,6 @@ "version": "1.8.2", "resolved": "https://registry.npmjs.org/prosemirror-dropcursor/-/prosemirror-dropcursor-1.8.2.tgz", "integrity": "sha512-CCk6Gyx9+Tt2sbYk5NK0nB1ukHi2ryaRgadV/LvyNuO3ena1payM2z6Cg0vO1ebK8cxbzo41ku2DE5Axj1Zuiw==", - "license": "MIT", "dependencies": { "prosemirror-state": "^1.0.0", "prosemirror-transform": "^1.1.0", @@ -7922,10 +7826,9 @@ } }, "node_modules/prosemirror-gapcursor": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/prosemirror-gapcursor/-/prosemirror-gapcursor-1.4.0.tgz", - "integrity": "sha512-z00qvurSdCEWUIulij/isHaqu4uLS8r/Fi61IbjdIPJEonQgggbJsLnstW7Lgdk4zQ68/yr6B6bf7sJXowIgdQ==", - "license": "MIT", + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/prosemirror-gapcursor/-/prosemirror-gapcursor-1.4.1.tgz", + "integrity": "sha512-pMdYaEnjNMSwl11yjEGtgTmLkR08m/Vl+Jj443167p9eB3HVQKhYCc4gmHVDsLPODfZfjr/MmirsdyZziXbQKw==", "dependencies": { "prosemirror-keymap": "^1.0.0", "prosemirror-model": "^1.0.0", @@ -7937,7 +7840,6 @@ "version": "1.5.0", "resolved": "https://registry.npmjs.org/prosemirror-history/-/prosemirror-history-1.5.0.tgz", "integrity": "sha512-zlzTiH01eKA55UAf1MEjtssJeHnGxO0j4K4Dpx+gnmX9n+SHNlDqI2oO1Kv1iPN5B1dm5fsljCfqKF9nFL6HRg==", - "license": "MIT", "dependencies": { "prosemirror-state": "^1.2.2", "prosemirror-transform": "^1.0.0", @@ -7945,72 +7847,27 @@ "rope-sequence": "^1.3.0" } }, - "node_modules/prosemirror-inputrules": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/prosemirror-inputrules/-/prosemirror-inputrules-1.5.1.tgz", - "integrity": "sha512-7wj4uMjKaXWAQ1CDgxNzNtR9AlsuwzHfdFH1ygEHA2KHF2DOEaXl1CJfNPAKCg9qNEh4rum975QLaCiQPyY6Fw==", - "license": "MIT", - "dependencies": { - "prosemirror-state": "^1.0.0", - "prosemirror-transform": "^1.0.0" - } - }, "node_modules/prosemirror-keymap": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/prosemirror-keymap/-/prosemirror-keymap-1.2.3.tgz", "integrity": "sha512-4HucRlpiLd1IPQQXNqeo81BGtkY8Ai5smHhKW9jjPKRc2wQIxksg7Hl1tTI2IfT2B/LgX6bfYvXxEpJl7aKYKw==", - "license": "MIT", "dependencies": { "prosemirror-state": "^1.0.0", "w3c-keyname": "^2.2.0" } }, - "node_modules/prosemirror-markdown": { - "version": "1.13.4", - "resolved": "https://registry.npmjs.org/prosemirror-markdown/-/prosemirror-markdown-1.13.4.tgz", - "integrity": "sha512-D98dm4cQ3Hs6EmjK500TdAOew4Z03EV71ajEFiWra3Upr7diytJsjF4mPV2dW+eK5uNectiRj0xFxYI9NLXDbw==", - "license": "MIT", - "dependencies": { - "@types/markdown-it": "^14.0.0", - "markdown-it": "^14.0.0", - "prosemirror-model": "^1.25.0" - } - }, - "node_modules/prosemirror-menu": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/prosemirror-menu/-/prosemirror-menu-1.2.5.tgz", - "integrity": "sha512-qwXzynnpBIeg1D7BAtjOusR+81xCp53j7iWu/IargiRZqRjGIlQuu1f3jFi+ehrHhWMLoyOQTSRx/IWZJqOYtQ==", - "license": "MIT", - "dependencies": { - "crelt": "^1.0.0", - "prosemirror-commands": "^1.0.0", - "prosemirror-history": "^1.0.0", - "prosemirror-state": "^1.0.0" - } - }, "node_modules/prosemirror-model": { "version": "1.25.4", "resolved": "https://registry.npmjs.org/prosemirror-model/-/prosemirror-model-1.25.4.tgz", "integrity": "sha512-PIM7E43PBxKce8OQeezAs9j4TP+5yDpZVbuurd1h5phUxEKIu+G2a+EUZzIC5nS1mJktDJWzbqS23n1tsAf5QA==", - "license": "MIT", "dependencies": { "orderedmap": "^2.0.0" } }, - "node_modules/prosemirror-schema-basic": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/prosemirror-schema-basic/-/prosemirror-schema-basic-1.2.4.tgz", - "integrity": "sha512-ELxP4TlX3yr2v5rM7Sb70SqStq5NvI15c0j9j/gjsrO5vaw+fnnpovCLEGIcpeGfifkuqJwl4fon6b+KdrODYQ==", - "license": "MIT", - "dependencies": { - "prosemirror-model": "^1.25.0" - } - }, "node_modules/prosemirror-schema-list": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/prosemirror-schema-list/-/prosemirror-schema-list-1.5.1.tgz", "integrity": "sha512-927lFx/uwyQaGwJxLWCZRkjXG0p48KpMj6ueoYiu4JX05GGuGcgzAy62dfiV8eFZftgyBUvLx76RsMe20fJl+Q==", - "license": "MIT", "dependencies": { "prosemirror-model": "^1.0.0", "prosemirror-state": "^1.0.0", @@ -8021,7 +7878,6 @@ "version": "1.4.4", "resolved": "https://registry.npmjs.org/prosemirror-state/-/prosemirror-state-1.4.4.tgz", "integrity": "sha512-6jiYHH2CIGbCfnxdHbXZ12gySFY/fz/ulZE333G6bPqIZ4F+TXo9ifiR86nAHpWnfoNjOb3o5ESi7J8Uz1jXHw==", - "license": "MIT", "dependencies": { "prosemirror-model": "^1.0.0", "prosemirror-transform": "^1.0.0", @@ -8032,7 +7888,6 @@ "version": "1.8.5", "resolved": "https://registry.npmjs.org/prosemirror-tables/-/prosemirror-tables-1.8.5.tgz", "integrity": "sha512-V/0cDCsHKHe/tfWkeCmthNUcEp1IVO3p6vwN8XtwE9PZQLAZJigbw3QoraAdfJPir4NKJtNvOB8oYGKRl+t0Dw==", - "license": "MIT", "dependencies": { "prosemirror-keymap": "^1.2.3", "prosemirror-model": "^1.25.4", @@ -8041,35 +7896,18 @@ "prosemirror-view": "^1.41.4" } }, - "node_modules/prosemirror-trailing-node": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/prosemirror-trailing-node/-/prosemirror-trailing-node-3.0.0.tgz", - "integrity": "sha512-xiun5/3q0w5eRnGYfNlW1uU9W6x5MoFKWwq/0TIRgt09lv7Hcser2QYV8t4muXbEr+Fwo0geYn79Xs4GKywrRQ==", - "license": "MIT", - "dependencies": { - "@remirror/core-constants": "3.0.0", - "escape-string-regexp": "^4.0.0" - }, - "peerDependencies": { - "prosemirror-model": "^1.22.1", - "prosemirror-state": "^1.4.2", - "prosemirror-view": "^1.33.8" - } - }, "node_modules/prosemirror-transform": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/prosemirror-transform/-/prosemirror-transform-1.11.0.tgz", - "integrity": "sha512-4I7Ce4KpygXb9bkiPS3hTEk4dSHorfRw8uI0pE8IhxlK2GXsqv5tIA7JUSxtSu7u8APVOTtbUBxTmnHIxVkIJw==", - "license": "MIT", + "version": "1.12.0", + "resolved": "https://registry.npmjs.org/prosemirror-transform/-/prosemirror-transform-1.12.0.tgz", + "integrity": "sha512-GxboyN4AMIsoHNtz5uf2r2Ru551i5hWeCMD6E2Ib4Eogqoub0NflniaBPVQ4MrGE5yZ8JV9tUHg9qcZTTrcN4w==", "dependencies": { "prosemirror-model": "^1.21.0" } }, "node_modules/prosemirror-view": { - "version": "1.41.6", - "resolved": "https://registry.npmjs.org/prosemirror-view/-/prosemirror-view-1.41.6.tgz", - "integrity": "sha512-mxpcDG4hNQa/CPtzxjdlir5bJFDlm0/x5nGBbStB2BWX+XOQ9M8ekEG+ojqB5BcVu2Rc80/jssCMZzSstJuSYg==", - "license": "MIT", + "version": "1.41.8", + "resolved": "https://registry.npmjs.org/prosemirror-view/-/prosemirror-view-1.41.8.tgz", + "integrity": "sha512-TnKDdohEatgyZNGCDWIdccOHXhYloJwbwU+phw/a23KBvJIR9lWQWW7WHHK3vBdOLDNuF7TaX98GObUZOWkOnA==", "dependencies": { "prosemirror-model": "^1.20.0", "prosemirror-state": "^1.0.0", @@ -8077,26 +7915,18 @@ } }, "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==", - "license": "MIT" + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-2.1.0.tgz", + "integrity": "sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==", + "engines": { + "node": ">=10" + } }, "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/punycode.js": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/punycode.js/-/punycode.js-2.3.1.tgz", - "integrity": "sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==", - "license": "MIT", "engines": { "node": ">=6" } @@ -8105,7 +7935,6 @@ "version": "1.4.3", "resolved": "https://registry.npmjs.org/radix-ui/-/radix-ui-1.4.3.tgz", "integrity": "sha512-aWizCQiyeAenIdUbqEpXgRA1ya65P13NKn/W8rWkcN0OPkRDxdBVLWnIEDsS2RpwCK2nobI7oMUSmexzTDyAmA==", - "license": "MIT", "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-accessible-icon": "1.1.7", @@ -8179,31 +8008,28 @@ } }, "node_modules/react": { - "version": "19.2.4", - "resolved": "https://registry.npmjs.org/react/-/react-19.2.4.tgz", - "integrity": "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==", - "license": "MIT", + "version": "19.2.5", + "resolved": "https://registry.npmjs.org/react/-/react-19.2.5.tgz", + "integrity": "sha512-llUJLzz1zTUBrskt2pwZgLq59AemifIftw4aB7JxOqf1HY2FDaGDxgwpAPVzHU1kdWabH7FauP4i1oEeer2WCA==", "engines": { "node": ">=0.10.0" } }, "node_modules/react-dom": { - "version": "19.2.4", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.4.tgz", - "integrity": "sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ==", - "license": "MIT", + "version": "19.2.5", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.5.tgz", + "integrity": "sha512-J5bAZz+DXMMwW/wV3xzKke59Af6CHY7G4uYLN1OvBcKEsWOs4pQExj86BBKamxl/Ik5bx9whOrvBlSDfWzgSag==", "dependencies": { "scheduler": "^0.27.0" }, "peerDependencies": { - "react": "^19.2.4" + "react": "^19.2.5" } }, "node_modules/react-hotkeys-hook": { "version": "5.2.4", "resolved": "https://registry.npmjs.org/react-hotkeys-hook/-/react-hotkeys-hook-5.2.4.tgz", "integrity": "sha512-BgKg+A1+TawkYluh5Bo4cTmcgMN5L29uhJbDUQdHwPX+qgXRjIPYU5kIDHyxnAwCkCBiu9V5OpB2mpyeluVF2A==", - "license": "MIT", "peerDependencies": { "react": ">=16.8.0", "react-dom": ">=16.8.0" @@ -8214,16 +8040,15 @@ "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", "dev": true, - "license": "MIT", "peer": true }, "node_modules/react-markdown": { - "version": "9.0.3", - "resolved": "https://registry.npmjs.org/react-markdown/-/react-markdown-9.0.3.tgz", - "integrity": "sha512-Yk7Z94dbgYTOrdk41Z74GoKA7rThnsbbqBTRYuxoe08qvfQ9tJVhmAKw6BJS/ZORG7kTy/s1QvYzSuaoBA1qfw==", - "license": "MIT", + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/react-markdown/-/react-markdown-10.1.0.tgz", + "integrity": "sha512-qKxVopLT/TyA6BX3Ue5NwabOsAzm0Q7kAPwq6L+wWDwisYs7R8vZ0nRXqq6rkueboxpkjvLGU9fWifiX/ZZFxQ==", "dependencies": { "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", "devlop": "^1.0.0", "hast-util-to-jsx-runtime": "^2.0.0", "html-url-attributes": "^3.0.0", @@ -8243,11 +8068,19 @@ "react": ">=18" } }, + "node_modules/react-refresh": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz", + "integrity": "sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/react-remove-scroll": { "version": "2.7.2", "resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.7.2.tgz", "integrity": "sha512-Iqb9NjCCTt6Hf+vOdNIZGdTiH1QSqr27H/Ek9sv/a97gfueI/5h1s3yRi1nngzMUaOOToin5dI1dXKdXiF+u0Q==", - "license": "MIT", "dependencies": { "react-remove-scroll-bar": "^2.3.7", "react-style-singleton": "^2.2.3", @@ -8272,7 +8105,6 @@ "version": "2.3.8", "resolved": "https://registry.npmjs.org/react-remove-scroll-bar/-/react-remove-scroll-bar-2.3.8.tgz", "integrity": "sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q==", - "license": "MIT", "dependencies": { "react-style-singleton": "^2.2.2", "tslib": "^2.0.0" @@ -8294,7 +8126,6 @@ "version": "2.2.3", "resolved": "https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.2.3.tgz", "integrity": "sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ==", - "license": "MIT", "dependencies": { "get-nonce": "^1.0.0", "tslib": "^2.0.0" @@ -8317,7 +8148,6 @@ "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz", "integrity": "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==", "dev": true, - "license": "MIT", "dependencies": { "indent-string": "^4.0.0", "strip-indent": "^3.0.0" @@ -8330,7 +8160,6 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/refractor/-/refractor-5.0.0.tgz", "integrity": "sha512-QXOrHQF5jOpjjLfiNk5GFnWhRXvxjUVnlFxkeDmewR5sXkr3iM46Zo+CnRR8B+MDVqkULW4EcLVcRBNOPXHosw==", - "license": "MIT", "dependencies": { "@types/hast": "^3.0.0", "@types/prismjs": "^1.0.0", @@ -8346,7 +8175,6 @@ "version": "13.0.2", "resolved": "https://registry.npmjs.org/rehype/-/rehype-13.0.2.tgz", "integrity": "sha512-j31mdaRFrwFRUIlxGeuPXXKWQxet52RBQRvCmzl5eCefn/KGbomK5GMHNMsOJf55fgo3qw5tST5neDuarDYR2A==", - "license": "MIT", "dependencies": { "@types/hast": "^3.0.0", "rehype-parse": "^9.0.0", @@ -8359,10 +8187,9 @@ } }, "node_modules/rehype-attr": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/rehype-attr/-/rehype-attr-3.0.3.tgz", - "integrity": "sha512-Up50Xfra8tyxnkJdCzLBIBtxOcB2M1xdeKe1324U06RAvSjYm7ULSeoM+b/nYPQPVd7jsXJ9+39IG1WAJPXONw==", - "license": "MIT", + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/rehype-attr/-/rehype-attr-4.0.2.tgz", + "integrity": "sha512-v4+gw7pvUVLbG/dUpLgBE6r3TWTBYJ7z+sfAH3zapmM5CKzk5+CopFQgr4gMR6OBSKl/qpI6HR7gv1Cbig0uow==", "dependencies": { "unified": "~11.0.0", "unist-util-visit": "~5.0.0" @@ -8378,7 +8205,6 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-5.0.0.tgz", "integrity": "sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg==", - "license": "MIT", "dependencies": { "@types/unist": "^3.0.0", "unist-util-is": "^6.0.0", @@ -8393,7 +8219,6 @@ "version": "7.1.0", "resolved": "https://registry.npmjs.org/rehype-autolink-headings/-/rehype-autolink-headings-7.1.0.tgz", "integrity": "sha512-rItO/pSdvnvsP4QRB1pmPiNHUskikqtPojZKJPPPAVx9Hj8i8TwMBhofrrAYRhYOOBZH9tgmG5lPqDLuIWPWmw==", - "license": "MIT", "dependencies": { "@types/hast": "^3.0.0", "@ungap/structured-clone": "^1.0.0", @@ -8411,7 +8236,6 @@ "version": "2.0.3", "resolved": "https://registry.npmjs.org/rehype-ignore/-/rehype-ignore-2.0.3.tgz", "integrity": "sha512-IzhP6/u/6sm49sdktuYSmeIuObWB+5yC/5eqVws8BhuGA9kY25/byz6uCy/Ravj6lXUShEd2ofHM5MyAIj86Sg==", - "license": "MIT", "dependencies": { "hast-util-select": "^6.0.0", "unified": "^11.0.0", @@ -8428,7 +8252,6 @@ "version": "9.0.1", "resolved": "https://registry.npmjs.org/rehype-parse/-/rehype-parse-9.0.1.tgz", "integrity": "sha512-ksCzCD0Fgfh7trPDxr2rSylbwq9iYDkSn8TCDmEJ49ljEUBxDVCzCHv7QNzZOfODanX4+bWQ4WZqLCRWYLfhag==", - "license": "MIT", "dependencies": { "@types/hast": "^3.0.0", "hast-util-from-html": "^2.0.0", @@ -8443,7 +8266,6 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/rehype-prism-plus/-/rehype-prism-plus-2.0.2.tgz", "integrity": "sha512-jTHb8ZtQHd2VWAAKeCINgv/8zNEF0+LesmwJak69GemoPVN9/8fGEARTvqOpKqmN57HwaM9z8UKBVNVJe8zggw==", - "license": "MIT", "dependencies": { "hast-util-to-string": "^3.0.1", "parse-numeric-range": "^1.3.0", @@ -8457,7 +8279,6 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/rehype-raw/-/rehype-raw-7.0.0.tgz", "integrity": "sha512-/aE8hCfKlQeA8LmyeyQvQF3eBiLRGNlfBJEvWH7ivp9sBqs7TNqBL5X3v157rM4IFETqDnIOO+z5M/biZbo9Ww==", - "license": "MIT", "dependencies": { "@types/hast": "^3.0.0", "hast-util-raw": "^9.0.0", @@ -8472,7 +8293,6 @@ "version": "4.0.4", "resolved": "https://registry.npmjs.org/rehype-rewrite/-/rehype-rewrite-4.0.4.tgz", "integrity": "sha512-L/FO96EOzSA6bzOam4DVu61/PB3AGKcSPXpa53yMIozoxH4qg1+bVZDF8zh1EsuxtSauAhzt5cCnvoplAaSLrw==", - "license": "MIT", "dependencies": { "hast-util-select": "^6.0.0", "unified": "^11.0.3", @@ -8489,7 +8309,6 @@ "version": "6.0.0", "resolved": "https://registry.npmjs.org/rehype-slug/-/rehype-slug-6.0.0.tgz", "integrity": "sha512-lWyvf/jwu+oS5+hL5eClVd3hNdmwM1kAC0BUvEGD19pajQMIzcNUd/k9GsfQ+FfECvX+JE+e9/btsKH0EjJT6A==", - "license": "MIT", "dependencies": { "@types/hast": "^3.0.0", "github-slugger": "^2.0.0", @@ -8506,7 +8325,6 @@ "version": "10.0.1", "resolved": "https://registry.npmjs.org/rehype-stringify/-/rehype-stringify-10.0.1.tgz", "integrity": "sha512-k9ecfXHmIPuFVI61B9DeLPN0qFHfawM6RsuX48hoqlaKSF61RskNjSm1lI8PhBEM0MRdLxVVm4WmTqJQccH9mA==", - "license": "MIT", "dependencies": { "@types/hast": "^3.0.0", "hast-util-to-html": "^9.0.0", @@ -8521,7 +8339,6 @@ "version": "4.0.1", "resolved": "https://registry.npmjs.org/remark-gfm/-/remark-gfm-4.0.1.tgz", "integrity": "sha512-1quofZ2RQ9EWdeN34S79+KExV1764+wCUGop5CPL1WGdD0ocPpu91lzPGbwWMECpEpd42kJGQwzRfyov9j4yNg==", - "license": "MIT", "dependencies": { "@types/mdast": "^4.0.0", "mdast-util-gfm": "^3.0.0", @@ -8539,7 +8356,6 @@ "version": "1.3.1", "resolved": "https://registry.npmjs.org/remark-github-blockquote-alert/-/remark-github-blockquote-alert-1.3.1.tgz", "integrity": "sha512-OPNnimcKeozWN1w8KVQEuHOxgN3L4rah8geMOLhA5vN9wITqU4FWD+G26tkEsCGHiOVDbISx+Se5rGZ+D1p0Jg==", - "license": "MIT", "dependencies": { "unist-util-visit": "^5.0.0" }, @@ -8554,7 +8370,6 @@ "version": "11.0.0", "resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-11.0.0.tgz", "integrity": "sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA==", - "license": "MIT", "dependencies": { "@types/mdast": "^4.0.0", "mdast-util-from-markdown": "^2.0.0", @@ -8570,7 +8385,6 @@ "version": "11.1.2", "resolved": "https://registry.npmjs.org/remark-rehype/-/remark-rehype-11.1.2.tgz", "integrity": "sha512-Dh7l57ianaEoIpzbp0PC9UKAdCSVklD8E5Rpw7ETfbTl3FqcOOgq5q2LVDhgGCkaBv7p24JXikPdvhhmHvKMsw==", - "license": "MIT", "dependencies": { "@types/hast": "^3.0.0", "@types/mdast": "^4.0.0", @@ -8587,7 +8401,6 @@ "version": "11.0.0", "resolved": "https://registry.npmjs.org/remark-stringify/-/remark-stringify-11.0.0.tgz", "integrity": "sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw==", - "license": "MIT", "dependencies": { "@types/mdast": "^4.0.0", "mdast-util-to-markdown": "^2.0.0", @@ -8603,17 +8416,15 @@ "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", "dev": true, - "license": "MIT", "engines": { "node": ">=4" } }, "node_modules/rollup": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.57.1.tgz", - "integrity": "sha512-oQL6lgK3e2QZeQ7gcgIkS2YZPg5slw37hYufJ3edKlfQSGGm8ICoxswK15ntSzF/a8+h7ekRy7k7oWc3BQ7y8A==", + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.60.2.tgz", + "integrity": "sha512-J9qZyW++QK/09NyN/zeO0dG/1GdGfyp9lV8ajHnRVLfo/uFsbji5mHnDgn/qYdUHyCkM2N+8VyspgZclfAh0eQ==", "dev": true, - "license": "MIT", "dependencies": { "@types/estree": "1.0.8" }, @@ -8625,60 +8436,84 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.57.1", - "@rollup/rollup-android-arm64": "4.57.1", - "@rollup/rollup-darwin-arm64": "4.57.1", - "@rollup/rollup-darwin-x64": "4.57.1", - "@rollup/rollup-freebsd-arm64": "4.57.1", - "@rollup/rollup-freebsd-x64": "4.57.1", - "@rollup/rollup-linux-arm-gnueabihf": "4.57.1", - "@rollup/rollup-linux-arm-musleabihf": "4.57.1", - "@rollup/rollup-linux-arm64-gnu": "4.57.1", - "@rollup/rollup-linux-arm64-musl": "4.57.1", - "@rollup/rollup-linux-loong64-gnu": "4.57.1", - "@rollup/rollup-linux-loong64-musl": "4.57.1", - "@rollup/rollup-linux-ppc64-gnu": "4.57.1", - "@rollup/rollup-linux-ppc64-musl": "4.57.1", - "@rollup/rollup-linux-riscv64-gnu": "4.57.1", - "@rollup/rollup-linux-riscv64-musl": "4.57.1", - "@rollup/rollup-linux-s390x-gnu": "4.57.1", - "@rollup/rollup-linux-x64-gnu": "4.57.1", - "@rollup/rollup-linux-x64-musl": "4.57.1", - "@rollup/rollup-openbsd-x64": "4.57.1", - "@rollup/rollup-openharmony-arm64": "4.57.1", - "@rollup/rollup-win32-arm64-msvc": "4.57.1", - "@rollup/rollup-win32-ia32-msvc": "4.57.1", - "@rollup/rollup-win32-x64-gnu": "4.57.1", - "@rollup/rollup-win32-x64-msvc": "4.57.1", + "@rollup/rollup-android-arm-eabi": "4.60.2", + "@rollup/rollup-android-arm64": "4.60.2", + "@rollup/rollup-darwin-arm64": "4.60.2", + "@rollup/rollup-darwin-x64": "4.60.2", + "@rollup/rollup-freebsd-arm64": "4.60.2", + "@rollup/rollup-freebsd-x64": "4.60.2", + "@rollup/rollup-linux-arm-gnueabihf": "4.60.2", + "@rollup/rollup-linux-arm-musleabihf": "4.60.2", + "@rollup/rollup-linux-arm64-gnu": "4.60.2", + "@rollup/rollup-linux-arm64-musl": "4.60.2", + "@rollup/rollup-linux-loong64-gnu": "4.60.2", + "@rollup/rollup-linux-loong64-musl": "4.60.2", + "@rollup/rollup-linux-ppc64-gnu": "4.60.2", + "@rollup/rollup-linux-ppc64-musl": "4.60.2", + "@rollup/rollup-linux-riscv64-gnu": "4.60.2", + "@rollup/rollup-linux-riscv64-musl": "4.60.2", + "@rollup/rollup-linux-s390x-gnu": "4.60.2", + "@rollup/rollup-linux-x64-gnu": "4.60.2", + "@rollup/rollup-linux-x64-musl": "4.60.2", + "@rollup/rollup-openbsd-x64": "4.60.2", + "@rollup/rollup-openharmony-arm64": "4.60.2", + "@rollup/rollup-win32-arm64-msvc": "4.60.2", + "@rollup/rollup-win32-ia32-msvc": "4.60.2", + "@rollup/rollup-win32-x64-gnu": "4.60.2", + "@rollup/rollup-win32-x64-msvc": "4.60.2", "fsevents": "~2.3.2" } }, + "node_modules/rollup/node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.60.2.tgz", + "integrity": "sha512-hr26p7e93Rl0Za+JwW7EAnwAvKkehh12BU1Llm9Ykiibg4uIr2rbpxG9WCf56GuvidlTG9KiiQT/TXT1yAWxTA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "peer": true + }, + "node_modules/rollup/node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.60.2.tgz", + "integrity": "sha512-bTsRGj6VlSdn/XD4CGyzMnzaBs9bsRxy79eTqTCBsA8TMIEky7qg48aPkvJvFe1HyzQ5oMZdg7AnVlWQSKLTnw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "peer": true + }, "node_modules/rope-sequence": { "version": "1.3.4", "resolved": "https://registry.npmjs.org/rope-sequence/-/rope-sequence-1.3.4.tgz", - "integrity": "sha512-UT5EDe2cu2E/6O4igUr5PSFs23nvvukicWHx6GnOPlHAiiYbzNuCRQCuiUdHJQcqKalLKlrYJnjY0ySGsXNQXQ==", - "license": "MIT" + "integrity": "sha512-UT5EDe2cu2E/6O4igUr5PSFs23nvvukicWHx6GnOPlHAiiYbzNuCRQCuiUdHJQcqKalLKlrYJnjY0ySGsXNQXQ==" }, "node_modules/rrweb-cssom": { "version": "0.7.1", "resolved": "https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.7.1.tgz", "integrity": "sha512-TrEMa7JGdVm0UThDJSx7ddw5nVm3UJS9o9CCIZ72B1vSyEZoziDqBYP3XIoi/12lKrJR8rE3jeFHMok2F/Mnsg==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "dev": true, - "license": "MIT" + "dev": true }, "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" }, @@ -8689,34 +8524,22 @@ "node_modules/scheduler": { "version": "0.27.0", "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", - "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", - "license": "MIT" + "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==" }, "node_modules/semver": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", - "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "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" - }, - "engines": { - "node": ">=10" } }, - "node_modules/shallowequal": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/shallowequal/-/shallowequal-1.1.0.tgz", - "integrity": "sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ==", - "license": "MIT" - }, "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" }, @@ -8729,7 +8552,6 @@ "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" } @@ -8738,15 +8560,13 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", - "dev": true, - "license": "ISC" + "dev": true }, "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" }, @@ -8759,7 +8579,6 @@ "resolved": "https://registry.npmjs.org/sirv/-/sirv-3.0.2.tgz", "integrity": "sha512-2wcC/oGxHis/BoHkkPwldgiPSYcpZK3JU28WoMVv55yHJgcZ8rlXvuG9iZggz+sU1d4bRgIGASwyWqjxu3FM0g==", "dev": true, - "license": "MIT", "dependencies": { "@polka/url": "^1.0.0-next.24", "mrmime": "^2.0.0", @@ -8773,7 +8592,7 @@ "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==", - "license": "BSD-3-Clause", + "dev": true, "engines": { "node": ">=0.10.0" } @@ -8782,7 +8601,6 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-2.0.2.tgz", "integrity": "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==", - "license": "MIT", "funding": { "type": "github", "url": "https://github.com/sponsors/wooorm" @@ -8792,22 +8610,19 @@ "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" + "dev": true }, "node_modules/std-env": { "version": "3.10.0", "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.10.0.tgz", "integrity": "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==", - "dev": true, - "license": "MIT" + "dev": true }, "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", @@ -8826,7 +8641,6 @@ "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", @@ -8840,15 +8654,13 @@ "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" + "dev": true }, "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" }, @@ -8860,7 +8672,6 @@ "version": "4.0.4", "resolved": "https://registry.npmjs.org/stringify-entities/-/stringify-entities-4.0.4.tgz", "integrity": "sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==", - "license": "MIT", "dependencies": { "character-entities-html4": "^2.0.0", "character-entities-legacy": "^3.0.0" @@ -8871,13 +8682,12 @@ } }, "node_modules/strip-ansi": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", - "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", + "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": { - "ansi-regex": "^6.0.1" + "ansi-regex": "^6.2.2" }, "engines": { "node": ">=12" @@ -8892,7 +8702,6 @@ "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" }, @@ -8905,7 +8714,6 @@ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", "dev": true, - "license": "MIT", "engines": { "node": ">=12" }, @@ -8918,7 +8726,6 @@ "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz", "integrity": "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==", "dev": true, - "license": "MIT", "dependencies": { "min-indent": "^1.0.0" }, @@ -8931,7 +8738,6 @@ "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", "dev": true, - "license": "MIT", "engines": { "node": ">=8" }, @@ -8943,7 +8749,6 @@ "version": "1.1.21", "resolved": "https://registry.npmjs.org/style-to-js/-/style-to-js-1.1.21.tgz", "integrity": "sha512-RjQetxJrrUJLQPHbLku6U/ocGtzyjbJMP9lCNK7Ag0CNh690nSH8woqWH9u16nMjYBAok+i7JO1NP2pOy8IsPQ==", - "license": "MIT", "dependencies": { "style-to-object": "1.0.14" } @@ -8952,26 +8757,19 @@ "version": "1.0.14", "resolved": "https://registry.npmjs.org/style-to-object/-/style-to-object-1.0.14.tgz", "integrity": "sha512-LIN7rULI0jBscWQYaSswptyderlarFkjQ+t79nzty8tcIAceVomEVlLzH5VP4Cmsv6MtKhs7qaAiwlcp+Mgaxw==", - "license": "MIT", "dependencies": { "inline-style-parser": "0.2.7" } }, "node_modules/styled-components": { - "version": "6.3.9", - "resolved": "https://registry.npmjs.org/styled-components/-/styled-components-6.3.9.tgz", - "integrity": "sha512-J72R4ltw0UBVUlEjTzI0gg2STOqlI9JBhQOL4Dxt7aJOnnSesy0qJDn4PYfMCafk9cWOaVg129Pesl5o+DIh0Q==", - "license": "MIT", + "version": "6.4.1", + "resolved": "https://registry.npmjs.org/styled-components/-/styled-components-6.4.1.tgz", + "integrity": "sha512-ADu2dF53esUzzM4I0ewxhxFtsDd6v4V6dNkg3vG0iFKhnt06sJneTZnRvujAosZwW0XD58IKgGMQoqri4wHRqg==", "dependencies": { "@emotion/is-prop-valid": "1.4.0", - "@emotion/unitless": "0.10.0", - "@types/stylis": "4.2.7", "css-to-react-native": "3.2.0", "csstype": "3.2.3", - "postcss": "8.4.49", - "shallowequal": "1.1.0", - "stylis": "4.3.6", - "tslib": "2.8.1" + "stylis": "4.3.6" }, "engines": { "node": ">= 16" @@ -8981,27 +8779,33 @@ "url": "https://opencollective.com/styled-components" }, "peerDependencies": { + "css-to-react-native": ">= 3.2.0", "react": ">= 16.8.0", - "react-dom": ">= 16.8.0" + "react-dom": ">= 16.8.0", + "react-native": ">= 0.68.0" }, "peerDependenciesMeta": { + "css-to-react-native": { + "optional": true + }, "react-dom": { "optional": true + }, + "react-native": { + "optional": true } } }, "node_modules/stylis": { "version": "4.3.6", "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.3.6.tgz", - "integrity": "sha512-yQ3rwFWRfwNUY7H5vpU0wfdkNSnvnJinhF9830Swlaxl03zsOjCfmX0ugac+3LtK0lYSgwL/KXc8oYL3mG4YFQ==", - "license": "MIT" + "integrity": "sha512-yQ3rwFWRfwNUY7H5vpU0wfdkNSnvnJinhF9830Swlaxl03zsOjCfmX0ugac+3LtK0lYSgwL/KXc8oYL3mG4YFQ==" }, "node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, - "license": "MIT", "dependencies": { "has-flag": "^4.0.0" }, @@ -9013,45 +8817,53 @@ "version": "3.2.4", "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/test-exclude": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-7.0.1.tgz", - "integrity": "sha512-pFYqmTw68LXVjeWJMST4+borgQP2AyMNbg1BpZh9LbyhUeNkeaPF9gzfPGUAnSMV3qPYdWUwDIjjCLiSDOl7vg==", + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-7.0.2.tgz", + "integrity": "sha512-u9E6A+ZDYdp7a4WnarkXPZOx8Ilz46+kby6p1yZ8zsGTz9gYa6FIS7lj2oezzNKmtdyyJNNmmXDppga5GB7kSw==", "dev": true, - "license": "ISC", "dependencies": { "@istanbuljs/schema": "^0.1.2", "glob": "^10.4.1", - "minimatch": "^9.0.4" + "minimatch": "^10.2.2" }, "engines": { "node": ">=18" } }, + "node_modules/test-exclude/node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true, + "engines": { + "node": "18 || 20 || >=22" + } + }, "node_modules/test-exclude/node_modules/brace-expansion": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", - "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz", + "integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==", "dev": true, - "license": "MIT", "dependencies": { - "balanced-match": "^1.0.0" + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" } }, "node_modules/test-exclude/node_modules/minimatch": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "version": "10.2.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", + "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", "dev": true, - "license": "ISC", "dependencies": { - "brace-expansion": "^2.0.1" + "brace-expansion": "^5.0.5" }, "engines": { - "node": ">=16 || 14 >=14.17" + "node": "18 || 20 || >=22" }, "funding": { "url": "https://github.com/sponsors/isaacs" @@ -9061,25 +8873,22 @@ "version": "2.9.0", "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/tinyexec": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.2.tgz", "integrity": "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==", - "dev": true, - "license": "MIT" + "dev": true }, "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" @@ -9093,7 +8902,6 @@ "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-1.1.1.tgz", "integrity": "sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==", "dev": true, - "license": "MIT", "engines": { "node": "^18.0.0 || >=20.0.0" } @@ -9103,7 +8911,6 @@ "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-1.2.0.tgz", "integrity": "sha512-weEDEq7Z5eTHPDh4xjX789+fHfF+P8boiFB+0vbWzpbnbsEr/GRaohi/uMKxg8RZMXnl1ItAi/IUHWMsjDV7kQ==", "dev": true, - "license": "MIT", "engines": { "node": ">=14.0.0" } @@ -9113,7 +8920,6 @@ "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-3.0.2.tgz", "integrity": "sha512-n1cw8k1k0x4pgA2+9XrOkFydTerNcJ1zWCO5Nn9scWHTD+5tp8dghT2x1uduQePZTZgd3Tupf+x9BxJjeJi77Q==", "dev": true, - "license": "MIT", "engines": { "node": ">=14.0.0" } @@ -9123,7 +8929,6 @@ "resolved": "https://registry.npmjs.org/tldts/-/tldts-6.1.86.tgz", "integrity": "sha512-WMi/OQ2axVTf/ykqCQgXiIct+mSQDFdH2fkwhPwgEwvJ1kSzZRiinb0zF2Xb8u4+OqPChmyI6MEu4EezNJz+FQ==", "dev": true, - "license": "MIT", "dependencies": { "tldts-core": "^6.1.86" }, @@ -9135,15 +8940,13 @@ "version": "6.1.86", "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-6.1.86.tgz", "integrity": "sha512-Je6p7pkk+KMzMv2XXKmAE3McmolOQFdxkKw0R8EYNr7sELW46JqnNeTX8ybPiQgvg1ymCoF8LXs5fzFaZvJPTA==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/totalist": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/totalist/-/totalist-3.0.1.tgz", "integrity": "sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==", "dev": true, - "license": "MIT", "engines": { "node": ">=6" } @@ -9153,7 +8956,6 @@ "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-5.1.2.tgz", "integrity": "sha512-FVDYdxtnj0G6Qm/DhNPSb8Ju59ULcup3tuJxkFb5K8Bv2pUXILbf0xZWU8PX8Ov19OXljbUyveOFwRMwkXzO+A==", "dev": true, - "license": "BSD-3-Clause", "dependencies": { "tldts": "^6.1.32" }, @@ -9166,7 +8968,6 @@ "resolved": "https://registry.npmjs.org/tr46/-/tr46-5.1.1.tgz", "integrity": "sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw==", "dev": true, - "license": "MIT", "dependencies": { "punycode": "^2.3.1" }, @@ -9178,7 +8979,6 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/trim-lines/-/trim-lines-3.0.1.tgz", "integrity": "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==", - "license": "MIT", "funding": { "type": "github", "url": "https://github.com/sponsors/wooorm" @@ -9188,18 +8988,16 @@ "version": "2.2.0", "resolved": "https://registry.npmjs.org/trough/-/trough-2.2.0.tgz", "integrity": "sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==", - "license": "MIT", "funding": { "type": "github", "url": "https://github.com/sponsors/wooorm" } }, "node_modules/ts-api-utils": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.4.0.tgz", - "integrity": "sha512-3TaVTaAv2gTiMB35i3FiGJaRfwb3Pyn/j3m/bfAvGe8FB7CF6u+LMYqYlDh7reQf7UNvoTvdfAqHGmPGOSsPmA==", + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.5.0.tgz", + "integrity": "sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==", "dev": true, - "license": "MIT", "engines": { "node": ">=18.12" }, @@ -9210,16 +9008,18 @@ "node_modules/tslib": { "version": "2.8.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "license": "0BSD" + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" }, "node_modules/turndown": { - "version": "7.2.2", - "resolved": "https://registry.npmjs.org/turndown/-/turndown-7.2.2.tgz", - "integrity": "sha512-1F7db8BiExOKxjSMU2b7if62D/XOyQyZbPKq/nUwopfgnHlqXHqQ0lvfUTeUIr1lZJzOPFn43dODyMSIfvWRKQ==", - "license": "MIT", + "version": "7.2.4", + "resolved": "https://registry.npmjs.org/turndown/-/turndown-7.2.4.tgz", + "integrity": "sha512-I8yFsfRzmzK0WV1pNNOA4A7y4RDfFxPRxb3t+e3ui14qSGOxGtiSP6GjeX+Y6CHb7HYaFj7ECUD7VE5kQMZWGQ==", "dependencies": { "@mixmark-io/domino": "^2.2.0" + }, + "engines": { + "node": ">=18", + "npm": ">=9" } }, "node_modules/type-check": { @@ -9227,7 +9027,6 @@ "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", "dev": true, - "license": "MIT", "dependencies": { "prelude-ls": "^1.2.1" }, @@ -9240,7 +9039,6 @@ "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "dev": true, - "license": "Apache-2.0", "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -9250,16 +9048,15 @@ } }, "node_modules/typescript-eslint": { - "version": "8.55.0", - "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.55.0.tgz", - "integrity": "sha512-HE4wj+r5lmDVS9gdaN0/+iqNvPZwGfnJ5lZuz7s5vLlg9ODw0bIiiETaios9LvFI1U94/VBXGm3CB2Y5cNFMpw==", + "version": "8.59.0", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.59.0.tgz", + "integrity": "sha512-BU3ONW9X+v90EcCH9ZS6LMackcVtxRLlI3XrYyqZIwVSHIk7Qf7bFw1z0M9Q0IUxhTMZCf8piY9hTYaNEIASrw==", "dev": true, - "license": "MIT", "dependencies": { - "@typescript-eslint/eslint-plugin": "8.55.0", - "@typescript-eslint/parser": "8.55.0", - "@typescript-eslint/typescript-estree": "8.55.0", - "@typescript-eslint/utils": "8.55.0" + "@typescript-eslint/eslint-plugin": "8.59.0", + "@typescript-eslint/parser": "8.59.0", + "@typescript-eslint/typescript-estree": "8.59.0", + "@typescript-eslint/utils": "8.59.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -9269,28 +9066,20 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0", - "typescript": ">=4.8.4 <6.0.0" + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" } }, - "node_modules/uc.micro": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-2.1.0.tgz", - "integrity": "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==", - "license": "MIT" - }, "node_modules/undici-types": { "version": "7.16.0", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/unified": { "version": "11.0.5", "resolved": "https://registry.npmjs.org/unified/-/unified-11.0.5.tgz", "integrity": "sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==", - "license": "MIT", "dependencies": { "@types/unist": "^3.0.0", "bail": "^2.0.0", @@ -9309,7 +9098,6 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/unist-util-filter/-/unist-util-filter-5.0.1.tgz", "integrity": "sha512-pHx7D4Zt6+TsfwylH9+lYhBhzyhEnCXs/lbq/Hstxno5z4gVdyc2WEW0asfjGKPyG4pEKrnBv5hdkO6+aRnQJw==", - "license": "MIT", "dependencies": { "@types/unist": "^3.0.0", "unist-util-is": "^6.0.0", @@ -9320,7 +9108,6 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-6.0.1.tgz", "integrity": "sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g==", - "license": "MIT", "dependencies": { "@types/unist": "^3.0.0" }, @@ -9333,7 +9120,6 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/unist-util-position/-/unist-util-position-5.0.0.tgz", "integrity": "sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==", - "license": "MIT", "dependencies": { "@types/unist": "^3.0.0" }, @@ -9346,7 +9132,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz", "integrity": "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==", - "license": "MIT", "dependencies": { "@types/unist": "^3.0.0" }, @@ -9359,7 +9144,6 @@ "version": "5.1.0", "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-5.1.0.tgz", "integrity": "sha512-m+vIdyeCOpdr/QeQCu2EzxX/ohgS8KbnPDgFni4dQsfSCtpz8UqDyY5GjRru8PDKuYn7Fq19j1CQ+nJSsGKOzg==", - "license": "MIT", "dependencies": { "@types/unist": "^3.0.0", "unist-util-is": "^6.0.0", @@ -9374,7 +9158,6 @@ "version": "6.0.2", "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-6.0.2.tgz", "integrity": "sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ==", - "license": "MIT", "dependencies": { "@types/unist": "^3.0.0", "unist-util-is": "^6.0.0" @@ -9384,12 +9167,41 @@ "url": "https://opencollective.com/unified" } }, + "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" + } + ], + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, "node_modules/uri-js": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", "dev": true, - "license": "BSD-2-Clause", "dependencies": { "punycode": "^2.1.0" } @@ -9398,7 +9210,6 @@ "version": "1.3.3", "resolved": "https://registry.npmjs.org/use-callback-ref/-/use-callback-ref-1.3.3.tgz", "integrity": "sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg==", - "license": "MIT", "dependencies": { "tslib": "^2.0.0" }, @@ -9419,7 +9230,6 @@ "version": "1.1.3", "resolved": "https://registry.npmjs.org/use-sidecar/-/use-sidecar-1.1.3.tgz", "integrity": "sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ==", - "license": "MIT", "dependencies": { "detect-node-es": "^1.1.0", "tslib": "^2.0.0" @@ -9441,7 +9251,6 @@ "version": "1.6.0", "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz", "integrity": "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==", - "license": "MIT", "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } @@ -9450,7 +9259,6 @@ "version": "6.0.3", "resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.3.tgz", "integrity": "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==", - "license": "MIT", "dependencies": { "@types/unist": "^3.0.0", "vfile-message": "^4.0.0" @@ -9464,7 +9272,6 @@ "version": "5.0.3", "resolved": "https://registry.npmjs.org/vfile-location/-/vfile-location-5.0.3.tgz", "integrity": "sha512-5yXvWDEgqeiYiBe1lbxYF7UMAIm/IcopxMHrMQDq3nvKcjPKIhZklUKL+AE7J7uApI4kwe2snsK+eI6UTj9EHg==", - "license": "MIT", "dependencies": { "@types/unist": "^3.0.0", "vfile": "^6.0.0" @@ -9478,7 +9285,6 @@ "version": "4.0.3", "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-4.0.3.tgz", "integrity": "sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw==", - "license": "MIT", "dependencies": { "@types/unist": "^3.0.0", "unist-util-stringify-position": "^4.0.0" @@ -9493,7 +9299,6 @@ "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.21.tgz", "integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==", "dev": true, - "license": "MIT", "dependencies": { "esbuild": "^0.21.3", "postcss": "^8.4.43", @@ -9553,7 +9358,6 @@ "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-2.1.9.tgz", "integrity": "sha512-AM9aQ/IPrW/6ENLQg3AGY4K1N2TGZdR5e4gu/MmmR2xR3Ll1+dib+nook92g4TV3PXVyeyxdWwtaCAiUL0hMxA==", "dev": true, - "license": "MIT", "dependencies": { "cac": "^6.7.14", "debug": "^4.3.7", @@ -9576,7 +9380,6 @@ "resolved": "https://registry.npmjs.org/vitest/-/vitest-2.1.9.tgz", "integrity": "sha512-MSmPM9REYqDGBI8439mA4mWhV5sKmDlBKWIYbA3lRb2PTHACE0mgKwA8yQ2xq9vxDTuk4iPrECBAEW2aoFXY0Q==", "dev": true, - "license": "MIT", "dependencies": { "@vitest/expect": "2.1.9", "@vitest/mocker": "2.1.9", @@ -9640,15 +9443,13 @@ "node_modules/w3c-keyname": { "version": "2.2.8", "resolved": "https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-2.2.8.tgz", - "integrity": "sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==", - "license": "MIT" + "integrity": "sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==" }, "node_modules/w3c-xmlserializer": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz", "integrity": "sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==", "dev": true, - "license": "MIT", "dependencies": { "xml-name-validator": "^5.0.0" }, @@ -9660,7 +9461,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/web-namespaces/-/web-namespaces-2.0.1.tgz", "integrity": "sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ==", - "license": "MIT", "funding": { "type": "github", "url": "https://github.com/sponsors/wooorm" @@ -9671,7 +9471,6 @@ "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", "dev": true, - "license": "BSD-2-Clause", "engines": { "node": ">=12" } @@ -9682,7 +9481,6 @@ "integrity": "sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==", "deprecated": "Use @exodus/bytes instead for a more spec-conformant and faster implementation", "dev": true, - "license": "MIT", "dependencies": { "iconv-lite": "0.6.3" }, @@ -9695,7 +9493,6 @@ "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz", "integrity": "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==", "dev": true, - "license": "MIT", "engines": { "node": ">=18" } @@ -9705,7 +9502,6 @@ "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-14.2.0.tgz", "integrity": "sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw==", "dev": true, - "license": "MIT", "dependencies": { "tr46": "^5.1.0", "webidl-conversions": "^7.0.0" @@ -9719,7 +9515,6 @@ "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" }, @@ -9735,7 +9530,6 @@ "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", "dev": true, - "license": "MIT", "dependencies": { "siginfo": "^2.0.0", "stackback": "0.0.2" @@ -9752,7 +9546,6 @@ "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", "dev": true, - "license": "MIT", "engines": { "node": ">=0.10.0" } @@ -9762,7 +9555,6 @@ "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", @@ -9781,7 +9573,6 @@ "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", @@ -9798,15 +9589,13 @@ "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" + "dev": true }, "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", @@ -9821,7 +9610,6 @@ "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" }, @@ -9834,7 +9622,6 @@ "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" }, @@ -9843,11 +9630,10 @@ } }, "node_modules/ws": { - "version": "8.19.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.19.0.tgz", - "integrity": "sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg==", + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.20.0.tgz", + "integrity": "sha512-sAt8BhgNbzCtgGbt2OxmpuryO63ZoDk/sqaB/znQm94T4fCEsy/yV+7CdC1kJhOU9lboAEU7R3kquuycDoibVA==", "dev": true, - "license": "MIT", "engines": { "node": ">=10.0.0" }, @@ -9869,7 +9655,6 @@ "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-5.0.0.tgz", "integrity": "sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==", "dev": true, - "license": "Apache-2.0", "engines": { "node": ">=18" } @@ -9878,15 +9663,19 @@ "version": "2.2.0", "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", - "dev": true, - "license": "MIT" + "dev": true + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true }, "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" }, @@ -9898,7 +9687,6 @@ "version": "2.0.4", "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz", "integrity": "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==", - "license": "MIT", "funding": { "type": "github", "url": "https://github.com/sponsors/wooorm" diff --git a/frontend/package.json b/frontend/package.json index 919ecec7..72c3b24d 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -21,6 +21,7 @@ "format:check": "prettier --check \"**/*.{ts,tsx,json,css,md}\"" }, "devDependencies": { + "@vitejs/plugin-react": "^4.7.0", "prettier": "^3.4.2", "typescript": "^5.9.3" }, @@ -30,9 +31,9 @@ "@tiptap/suggestion": "^3.20.1" }, "optionalDependencies": { - "@rollup/rollup-linux-x64-musl": "4.57.1", "@rollup/rollup-linux-arm64-musl": "4.57.1", - "@swc/core-linux-x64-musl": "1.15.11", - "@swc/core-linux-arm64-musl": "1.15.11" + "@rollup/rollup-linux-x64-musl": "4.57.1", + "@swc/core-linux-arm64-musl": "1.15.11", + "@swc/core-linux-x64-musl": "1.15.11" } } diff --git a/frontend/packages/app/.env.example b/frontend/packages/app/.env.example index bc23100a..d5ac9a0f 100644 --- a/frontend/packages/app/.env.example +++ b/frontend/packages/app/.env.example @@ -1,8 +1,10 @@ VITE_API_BASE_URL=http://localhost:8000 VITE_WS_URL=ws://localhost:8000/ws -# Authentication provider: "none" for email-based login, "google" for Google OAuth -VITE_AUTH_PROVIDER=none +# Authentication provider: "none" | "google" | "shotgrid" +# "shotgrid" is the recommended mode for production deployments. +# Mode (pat vs sso) is auto-detected from the backend SHOTGRID_AUTH_MODE env var. +VITE_AUTH_PROVIDER=shotgrid # Required only when VITE_AUTH_PROVIDER=google VITE_GOOGLE_CLIENT_ID=your-google-client-id.apps.googleusercontent.com diff --git a/frontend/packages/app/src/App.tsx b/frontend/packages/app/src/App.tsx index 97bf8434..4a6bb062 100644 --- a/frontend/packages/app/src/App.tsx +++ b/frontend/packages/app/src/App.tsx @@ -5,6 +5,7 @@ import { Layout, ContentArea, ProjectSelector, + ShotGridLoginPage, } from './components'; import { useAuth } from './contexts'; import { useGetVersionsForPlaylist } from './api'; @@ -12,7 +13,7 @@ import { usePlaylistMetadata } from './hooks/usePlaylistMetadata'; function App() { const queryClient = useQueryClient(); - const { signOut } = useAuth(); + const { isAuthenticated, isLoading, authProvider, signOut } = useAuth(); const [selectedProject, setSelectedProject] = useState(null); const [selectedPlaylist, setSelectedPlaylist] = useState( null @@ -85,6 +86,11 @@ function App() { setSelectedVersion(version); }; + // ShotGrid auth: show login page until authenticated + if (authProvider === 'shotgrid' && (isLoading || !isAuthenticated)) { + return ; + } + if (!selectedProject || !selectedPlaylist || !userEmail) { return ; } diff --git a/frontend/packages/app/src/components/ProjectSelector.tsx b/frontend/packages/app/src/components/ProjectSelector.tsx index 4a47baec..04d86866 100644 --- a/frontend/packages/app/src/components/ProjectSelector.tsx +++ b/frontend/packages/app/src/components/ProjectSelector.tsx @@ -206,54 +206,6 @@ const SubmitButton = styled.button` } `; -const GoogleButton = styled.button` - display: flex; - align-items: center; - justify-content: center; - gap: 12px; - width: 100%; - padding: 12px 24px; - font-family: ${({ theme }) => theme.fonts.sans}; - font-size: 15px; - font-weight: 500; - color: ${({ theme }) => theme.colors.text.primary}; - background: ${({ theme }) => theme.colors.bg.surface}; - border: 1px solid ${({ theme }) => theme.colors.border.subtle}; - border-radius: ${({ theme }) => theme.radii.md}; - cursor: pointer; - transition: all ${({ theme }) => theme.transitions.fast}; - - &:hover { - background: ${({ theme }) => theme.colors.bg.surfaceHover}; - border-color: ${({ theme }) => theme.colors.border.default}; - } - - &:disabled { - opacity: 0.6; - cursor: not-allowed; - } -`; - -const GoogleIcon = () => ( - - - - - - -); function getStoredProject(): Project | null { try { @@ -428,14 +380,6 @@ export function ProjectSelector({ onSelectionComplete }: ProjectSelectorProps) { )} - {step === 'signin' && authProvider === 'google' && ( - - - - Sign in with Google - - - )} {step === 'project' && ( diff --git a/frontend/packages/app/src/components/ShotGridLoginPage.tsx b/frontend/packages/app/src/components/ShotGridLoginPage.tsx new file mode 100644 index 00000000..b401f5bb --- /dev/null +++ b/frontend/packages/app/src/components/ShotGridLoginPage.tsx @@ -0,0 +1,160 @@ +import { useState } from 'react'; +import styled from 'styled-components'; +import { Button, Flex, Spinner, TextField } from '@radix-ui/themes'; +import { Logo } from './Logo'; +import { useShotGridAuth } from '../contexts/ShotGridAuthContext'; + +// ── Styled components ──────────────────────────────────────────────────── // + +const PageWrapper = styled.div` + min-height: 100vh; + display: flex; + align-items: center; + justify-content: center; + background: + radial-gradient( + ellipse 80% 50% at 50% -20%, + ${({ theme }) => theme.colors.accent.subtle}, + transparent + ) + fixed, + ${({ theme }) => theme.colors.bg.base}; +`; + +const Card = styled.div` + width: 100%; + max-width: 440px; + padding: 40px; + background: ${({ theme }) => theme.colors.bg.elevated}; + border: 1px solid ${({ theme }) => theme.colors.border.subtle}; + border-radius: ${({ theme }) => theme.radii.xl}; + box-shadow: ${({ theme }) => theme.shadows.lg}; +`; + +const LogoWrapper = styled.div` + display: flex; + justify-content: center; + margin-bottom: 32px; +`; + +const Title = styled.h1` + font-family: ${({ theme }) => theme.fonts.sans}; + font-size: 24px; + font-weight: 600; + color: ${({ theme }) => theme.colors.text.primary}; + text-align: center; + margin: 0 0 8px 0; +`; + +const Subtitle = styled.p` + font-family: ${({ theme }) => theme.fonts.sans}; + font-size: 14px; + color: ${({ theme }) => theme.colors.text.muted}; + text-align: center; + margin: 0 0 28px 0; +`; + +const FieldLabel = styled.label` + font-family: ${({ theme }) => theme.fonts.sans}; + font-size: 14px; + font-weight: 500; + color: ${({ theme }) => theme.colors.text.secondary}; + display: block; + margin-bottom: 6px; +`; + +const ErrorText = styled.p` + font-family: ${({ theme }) => theme.fonts.sans}; + font-size: 13px; + color: ${({ theme }) => theme.colors.status.error}; + text-align: center; + margin: 8px 0 0 0; +`; + +const HelpText = styled.p` + font-family: ${({ theme }) => theme.fonts.sans}; + font-size: 12px; + color: ${({ theme }) => theme.colors.text.muted}; + margin: 16px 0 0 0; + line-height: 1.5; +`; + +// ── Component ──────────────────────────────────────────────────────────── // + +export function ShotGridLoginPage() { + const { signIn, isLoading } = useShotGridAuth(); + + const [username, setUsername] = useState(''); + const [password, setPassword] = useState(''); + const [error, setError] = useState(null); + + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + setError(null); + try { + await signIn(username, password); + } catch (err) { + setError(err instanceof Error ? err.message : 'Login failed. Please try again.'); + } + }; + + return ( + + + + + + + Welcome to DNA + Sign in with your ShotGrid credentials + +
+ +
+ Email / Username + setUsername(e.target.value)} + disabled={isLoading} + autoComplete="username" + required + /> +
+ +
+ Password + setPassword(e.target.value)} + disabled={isLoading} + autoComplete="current-password" + required + /> +
+ + {error && {error}} + + +
+
+ + + Cloud ShotGrid: Use your ShotGrid Legacy Login password. If + you haven't set one, go to{' '} + Account Settings → Legacy Login and Personal Access Token in ShotGrid + and bind your Personal Access Token (generated at profile.autodesk.com). +
+ On-prem ShotGrid: Use your regular ShotGrid or LDAP password. +
+
+
+ ); +} diff --git a/frontend/packages/app/src/components/index.ts b/frontend/packages/app/src/components/index.ts index a488f959..e57a10c0 100644 --- a/frontend/packages/app/src/components/index.ts +++ b/frontend/packages/app/src/components/index.ts @@ -26,3 +26,4 @@ export { TranscriptPanel } from './TranscriptPanel'; export { SettingsModal } from './SettingsModal'; export { EntityPill } from './EntityPill/EntityPill'; export { EntitySearchInput } from './EntitySearchInput'; +export { ShotGridLoginPage } from './ShotGridLoginPage'; diff --git a/frontend/packages/app/src/contexts/AuthContext.tsx b/frontend/packages/app/src/contexts/AuthContext.tsx index bf2ad9d3..6325c360 100644 --- a/frontend/packages/app/src/contexts/AuthContext.tsx +++ b/frontend/packages/app/src/contexts/AuthContext.tsx @@ -6,13 +6,13 @@ import { useCallback, type ReactNode, } from 'react'; -import { GoogleOAuthProvider, useGoogleLogin } from '@react-oauth/google'; import { apiHandler } from '../api'; +import { ShotGridAuthProvider, useShotGridAuth } from './ShotGridAuthContext'; const STORAGE_KEY = 'dna-auth-token'; const USER_STORAGE_KEY = 'dna-auth-user'; -export type AuthProviderType = 'none' | 'google'; +export type AuthProviderType = 'none' | 'shotgrid'; export interface AuthUser { id: string; @@ -35,11 +35,9 @@ interface AuthContextValue { const AuthContext = createContext(null); function getAuthProvider(): AuthProviderType { - const provider = import.meta.env.VITE_AUTH_PROVIDER || 'google'; - if (provider === 'none' || provider === 'google') { - return provider; - } - return 'google'; + const provider = import.meta.env.VITE_AUTH_PROVIDER || 'none'; + if (provider === 'shotgrid') return 'shotgrid'; + return 'none'; } interface NoopAuthProviderInnerProps { @@ -50,39 +48,35 @@ function NoopAuthProviderInner({ children }: NoopAuthProviderInnerProps) { const [user, setUser] = useState(() => { const stored = localStorage.getItem(USER_STORAGE_KEY); if (stored) { - try { - return JSON.parse(stored); - } catch { - return null; - } + try { return JSON.parse(stored); } catch { return null; } } return null; }); - const [token, setToken] = useState(() => { - return localStorage.getItem(STORAGE_KEY); - }); - - useEffect(() => { - if (token !== 'noop-token' || !user?.email) return; - localStorage.setItem(STORAGE_KEY, user.email); - setToken(user.email); - }, [token, user?.email]); - - useEffect(() => { - const authToken = - token === 'noop-token' && user?.email ? user.email : token; - if (authToken && user) { - apiHandler.setUser({ - id: user.id, - email: user.email, - name: user.name, - token: authToken, - }); - } else { - apiHandler.setUser(null); - } - }, [token, user]); +const [token, setToken] = useState(() => { + return sessionStorage.getItem(STORAGE_KEY); +}); + +useEffect(() => { + if (token !== 'noop-token' || !user?.email) return; + sessionStorage.setItem(STORAGE_KEY, user.email); + setToken(user.email); +}, [token, user?.email]); + +useEffect(() => { + const authToken = + token === 'noop-token' && user?.email ? user.email : token; + if (authToken && user) { + apiHandler.setUser({ + id: user.id, + email: user.email, + name: user.name, + token: authToken, + }); + } else { + apiHandler.setUser(null); + } +}, [token, user]); const handleSignOut = useCallback(() => { localStorage.removeItem(STORAGE_KEY); @@ -93,6 +87,7 @@ function NoopAuthProviderInner({ children }: NoopAuthProviderInnerProps) { }, []); const handleSignInWithEmail = useCallback((email: string) => { +<<<<<<< HEAD const authUser: AuthUser = { id: email, email: email, @@ -102,6 +97,11 @@ function NoopAuthProviderInner({ children }: NoopAuthProviderInnerProps) { localStorage.setItem(STORAGE_KEY, email); localStorage.setItem(USER_STORAGE_KEY, JSON.stringify(authUser)); +======= + const authUser: AuthUser = { id: email, email, name: email.split('@')[0] }; + localStorage.setItem(STORAGE_KEY, email); + localStorage.setItem(USER_STORAGE_KEY, JSON.stringify(authUser)); +>>>>>>> 3328c7f (feat(auth): add ShotGrid PAT authentication for backend API endpoints) setToken(email); setUser(authUser); }, []); @@ -120,106 +120,23 @@ function NoopAuthProviderInner({ children }: NoopAuthProviderInnerProps) { return {children}; } -interface GoogleAuthProviderInnerProps { - children: ReactNode; -} - -function GoogleAuthProviderInner({ children }: GoogleAuthProviderInnerProps) { - const [user, setUser] = useState(() => { - const stored = localStorage.getItem(USER_STORAGE_KEY); - if (stored) { - try { - return JSON.parse(stored); - } catch { - return null; - } - } - return null; - }); - - const [token, setToken] = useState(() => { - return localStorage.getItem(STORAGE_KEY); - }); - - const [isLoading, setIsLoading] = useState(false); - - useEffect(() => { - if (token && user) { - apiHandler.setUser({ - id: user.id, - email: user.email, - name: user.name, - token: token, - }); - } else { - apiHandler.setUser(null); - } - }, [token, user]); - - const handleSignOut = useCallback(() => { - localStorage.removeItem(STORAGE_KEY); - localStorage.removeItem(USER_STORAGE_KEY); - setToken(null); - setUser(null); - apiHandler.setUser(null); - }, []); - - const googleLogin = useGoogleLogin({ - onSuccess: async (tokenResponse) => { - setIsLoading(true); - try { - const userInfoResponse = await fetch( - 'https://www.googleapis.com/oauth2/v3/userinfo', - { - headers: { Authorization: `Bearer ${tokenResponse.access_token}` }, - } - ); - const userInfo = await userInfoResponse.json(); - - const authUser: AuthUser = { - id: userInfo.sub, - email: userInfo.email, - name: userInfo.name, - picture: userInfo.picture, - }; - - const accessToken = tokenResponse.access_token; - - localStorage.setItem(STORAGE_KEY, accessToken); - localStorage.setItem(USER_STORAGE_KEY, JSON.stringify(authUser)); - - setToken(accessToken); - setUser(authUser); - } catch (error) { - console.error('Failed to get user info:', error); - handleSignOut(); - } finally { - setIsLoading(false); - } - }, - onError: (error) => { - console.error('Google login failed:', error); - setIsLoading(false); - }, - scope: 'openid email profile', - flow: 'implicit', - }); - - const handleSignIn = useCallback(() => { - setIsLoading(true); - googleLogin(); - }, [googleLogin]); +// Adapter: bridges ShotGridAuthContext into the shared AuthContext shape so +// all existing components using useAuth() continue to work unchanged. +function ShotGridAuthAdapterInner({ children }: { children: ReactNode }) { + const sg = useShotGridAuth(); const value: AuthContextValue = { - isAuthenticated: !!token && !!user, - isLoading, - user, - token, - authProvider: 'google', - signIn: handleSignIn, - signInWithEmail: () => - console.warn('Use signIn for Google auth provider'), - signOut: handleSignOut, + isAuthenticated: sg.isAuthenticated, + isLoading: sg.isLoading, + user: sg.user + ? { id: String(sg.user.id), email: sg.user.email, name: sg.user.name } + : null, + token: sg.token, + authProvider: 'shotgrid', + signIn: () => console.warn('Use ShotGridLoginPage for ShotGrid auth'), + signInWithEmail: (email) => + console.warn(`signInWithEmail(${email}) is not supported with ShotGrid PAT auth`), + signOut: sg.signOut, }; return {children}; @@ -227,37 +144,25 @@ function GoogleAuthProviderInner({ children }: GoogleAuthProviderInnerProps) { interface AuthProviderProps { children: ReactNode; - clientId?: string; } -export function AuthProvider({ children, clientId }: AuthProviderProps) { +export function AuthProvider({ children }: AuthProviderProps) { const authProviderType = getAuthProvider(); - if (authProviderType === 'none') { - return {children}; - } - - const googleClientId = - clientId || import.meta.env.VITE_GOOGLE_CLIENT_ID || ''; - - if (!googleClientId) { - console.warn( - 'VITE_GOOGLE_CLIENT_ID is not set. Falling back to noop auth.' + if (authProviderType === 'shotgrid') { + return ( + + {children} + ); - return {children}; } - return ( - - {children} - - ); + // AUTH_PROVIDER=none — development/testing only + return {children}; } export function useAuth(): AuthContextValue { const ctx = useContext(AuthContext); - if (!ctx) { - throw new Error('useAuth must be used within AuthProvider'); - } + if (!ctx) throw new Error('useAuth must be used within AuthProvider'); return ctx; } diff --git a/frontend/packages/app/src/contexts/ShotGridAuthContext.tsx b/frontend/packages/app/src/contexts/ShotGridAuthContext.tsx new file mode 100644 index 00000000..ef7970a4 --- /dev/null +++ b/frontend/packages/app/src/contexts/ShotGridAuthContext.tsx @@ -0,0 +1,221 @@ +import { + createContext, + useContext, + useState, + useEffect, + useCallback, + useRef, + type ReactNode, +} from 'react'; +import { apiHandler } from '../api'; + +const TOKEN_KEY = 'dna-sg-token'; +const USER_KEY = 'dna-sg-user'; + +// JWT auto-refresh 25 minutes before expiry (token lifetime is 480 min = 8 h) +const REFRESH_INTERVAL_MS = 25 * 60 * 1000; + +export interface ShotGridUser { + id: number | string; + email: string; + name: string; + shotgrid_user_id?: number; +} + +interface ShotGridAuthContextValue { + isAuthenticated: boolean; + isLoading: boolean; + user: ShotGridUser | null; + token: string | null; + authProvider: 'shotgrid'; + /** ShotGrid PAT (username + password) login */ + signIn: (username: string, password: string) => Promise; + signOut: () => Promise; + refreshToken: () => Promise; +} + +const ShotGridAuthContext = createContext(null); + +interface ShotGridAuthProviderProps { + children: ReactNode; +} + +export function ShotGridAuthProvider({ children }: ShotGridAuthProviderProps) { + const [isLoading, setIsLoading] = useState(true); + const [user, setUser] = useState(() => { + const stored = sessionStorage.getItem(USER_KEY); + if (stored) { + try { return JSON.parse(stored); } catch { return null; } + } + return null; + }); + const [token, setToken] = useState( + () => sessionStorage.getItem(TOKEN_KEY) + ); + + const refreshTimerRef = useRef | null>(null); + const apiBase = import.meta.env.VITE_API_BASE_URL || 'http://localhost:8000'; + + // ── Helpers ──────────────────────────────────────────────────────────── // + + const persist = useCallback((jwt: string, authUser: ShotGridUser) => { + sessionStorage.setItem(TOKEN_KEY, jwt); + sessionStorage.setItem(USER_KEY, JSON.stringify(authUser)); + setToken(jwt); + setUser(authUser); + apiHandler.setUser({ id: String(authUser.id), email: authUser.email, name: authUser.name, token: jwt }); + }, []); + + const clear = useCallback(() => { + sessionStorage.removeItem(TOKEN_KEY); + sessionStorage.removeItem(USER_KEY); + setToken(null); + setUser(null); + apiHandler.setUser(null); + }, []); + + // ── Validate stored token + set loading false on mount ───────────────── // + + useEffect(() => { + let cancelled = false; + (async () => { + // Validate stored token on mount. + // Clears the token on ANY failure — network error OR non-2xx response. + // This ensures a backend restart (which wipes MongoDB sessions) forces + // the user back to the login page instead of letting them reach the + // app with a dead session that returns 401 on every API call. + const storedToken = sessionStorage.getItem(TOKEN_KEY); + if (storedToken) { + // Only clear the token on a definitive rejection (401 / 403). + // Network errors (backend still starting, transient connectivity) are + // not treated as token invalidation — the user would lose their session + // every time the page is opened during a backend restart. + try { + const meRes = await fetch(`${apiBase}/auth/me`, { + headers: { Authorization: `Bearer ${storedToken}` }, + }); + const shouldClear = meRes.status === 401 || meRes.status === 403; + if (shouldClear && !cancelled) { + sessionStorage.removeItem(TOKEN_KEY); + sessionStorage.removeItem(USER_KEY); + setToken(null); + setUser(null); + apiHandler.setUser(null); + } + } catch { + // Network error — keep the stored token; the user will get a 401 + // on their first real API call if the session truly expired. + } + } + + if (!cancelled) setIsLoading(false); + })(); + return () => { cancelled = true; }; + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + // ── PAT sign-in ──────────────────────────────────────────────────────── // + + const signIn = useCallback(async (username: string, password: string) => { + setIsLoading(true); + try { + const res = await fetch(`${apiBase}/auth/login`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ username, password }), + }); + if (!res.ok) { + const err = await res.json().catch(() => ({})); + throw new Error(err.detail || 'Login failed'); + } + const data = await res.json(); + persist(data.access_token, { + id: data.user.id, + email: data.user.email, + name: data.user.name, + shotgrid_user_id: data.user.shotgrid_user_id, + }); + } finally { + setIsLoading(false); + } + }, [apiBase, persist]); + + // ── Token refresh ────────────────────────────────────────────────────── // + + const refreshToken = useCallback(async () => { + const currentToken = sessionStorage.getItem(TOKEN_KEY); + if (!currentToken) return; + try { + const res = await fetch(`${apiBase}/auth/refresh`, { + method: 'POST', + headers: { Authorization: `Bearer ${currentToken}` }, + }); + if (!res.ok) { clear(); return; } + const data = await res.json(); + const currentUser = sessionStorage.getItem(USER_KEY); + const parsedUser: ShotGridUser | null = currentUser ? JSON.parse(currentUser) : null; + if (parsedUser) { + persist(data.access_token, { ...parsedUser, ...data.user }); + } + } catch (err) { + console.error('[ShotGridAuth] Token refresh failed:', err); + clear(); + } + }, [apiBase, persist, clear]); + + // Auto-refresh every 25 minutes + useEffect(() => { + if (!token) return; + refreshTimerRef.current = setInterval(refreshToken, REFRESH_INTERVAL_MS); + return () => { + if (refreshTimerRef.current) clearInterval(refreshTimerRef.current); + }; + }, [token, refreshToken]); + + // Restore apiHandler on mount if token already in sessionStorage + useEffect(() => { + if (token && user) { + apiHandler.setUser({ id: String(user.id), email: user.email, name: user.name, token }); + } + // Run only on mount + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + // ── Sign-out ─────────────────────────────────────────────────────────── // + + const signOut = useCallback(async () => { + const currentToken = sessionStorage.getItem(TOKEN_KEY); + if (currentToken) { + try { + await fetch(`${apiBase}/auth/logout`, { + method: 'POST', + headers: { Authorization: `Bearer ${currentToken}` }, + }); + } catch { /* best-effort */ } + } + clear(); + }, [apiBase, clear]); + + const value: ShotGridAuthContextValue = { + isAuthenticated: !!token && !!user, + isLoading, + user, + token, + authProvider: 'shotgrid', + signIn, + signOut, + refreshToken, + }; + + return ( + + {children} + + ); +} + +export function useShotGridAuth(): ShotGridAuthContextValue { + const ctx = useContext(ShotGridAuthContext); + if (!ctx) throw new Error('useShotGridAuth must be used within ShotGridAuthProvider'); + return ctx; +} diff --git a/frontend/packages/app/src/contexts/index.ts b/frontend/packages/app/src/contexts/index.ts index 3a04b087..a79e5292 100644 --- a/frontend/packages/app/src/contexts/index.ts +++ b/frontend/packages/app/src/contexts/index.ts @@ -5,3 +5,5 @@ export { ThemeModeProvider, useThemeMode } from './ThemeContext'; export type { ThemeMode } from './ThemeContext'; export { AuthProvider, useAuth } from './AuthContext'; export type { AuthUser } from './AuthContext'; +export { ShotGridAuthProvider, useShotGridAuth } from './ShotGridAuthContext'; +export type { ShotGridUser } from './ShotGridAuthContext'; diff --git a/frontend/packages/app/tsconfig.json b/frontend/packages/app/tsconfig.json index 33ca26c3..710c64f4 100644 --- a/frontend/packages/app/tsconfig.json +++ b/frontend/packages/app/tsconfig.json @@ -24,7 +24,8 @@ /* Path aliases */ "baseUrl": ".", "paths": { - "@dna/core": ["../core/src"] + "@dna/core": ["../core/src"], + "@dna/core/*": ["../core/src/*"] } }, "include": ["src"] diff --git a/frontend/packages/app/vite.config.ts b/frontend/packages/app/vite.config.ts index 8b1a53de..65ed568c 100644 --- a/frontend/packages/app/vite.config.ts +++ b/frontend/packages/app/vite.config.ts @@ -1,8 +1,12 @@ import { defineConfig } from 'vite'; import react from '@vitejs/plugin-react-swc'; import path from 'path'; +import { fileURLToPath } from 'url'; + + +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); -// https://vite.dev/config/ export default defineConfig({ plugins: [react()], resolve: { @@ -10,4 +14,4 @@ export default defineConfig({ '@dna/core': path.resolve(__dirname, '../core/src'), }, }, -}); +}); \ No newline at end of file From 2f2dd51fe45393006d6876d288aaf5a2cee07492 Mon Sep 17 00:00:00 2001 From: Srijan9211 <40487030+Srijan9211@users.noreply.github.com> Date: Tue, 26 May 2026 23:49:05 +0530 Subject: [PATCH 2/2] fix: resolve merge conflicts and fix auth storage/session bugs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Resolve two leftover conflict markers (AuthContext.tsx, shotgrid.py). AuthContext — NoopAuthProviderInner storage fix: - All reads, writes and clears now use sessionStorage consistently; previously token was read from sessionStorage but written to localStorage on sign-in and removed from localStorage on sign-out, causing silent logout on every page reload and sign-out leaving the token in place - Fix indentation of useState/useEffect blocks broken during merge ShotGridAuthContext — mount validation overhaul: - Parse /auth/me 200 response and call persist() so user state is restored even when USER_KEY is absent (e.g. cleared by another tab) - Replace inline sessionStorage calls in validation path with clear() so future changes to clear() apply everywhere automatically - Remove racing apiHandler.setUser restore effect; validation effect now handles all cases (200 ok, 401/403, network error, 5xx) in one place - Add timer cancel inside clear() so auto-refresh stops on logout/401 Backend: - Add default values to ShotGridCredentials.username and access_token so sessions stored before these fields were added deserialise safely instead of raising TypeError and being silently deleted on deploy - Guard auth_provider is not None before calling get_user_email() in get_current_user to prevent AttributeError 500 when provider is absent Closes #55 Signed-off-by: Srijan --- backend/src/dna/auth/session_store.py | 7 +- .../src/dna/prodtrack_providers/shotgrid.py | 3 - backend/src/main.py | 2 +- .../packages/app/src/contexts/AuthContext.tsx | 69 +++++++------- .../app/src/contexts/ShotGridAuthContext.tsx | 91 ++++++++++++------- 5 files changed, 98 insertions(+), 74 deletions(-) diff --git a/backend/src/dna/auth/session_store.py b/backend/src/dna/auth/session_store.py index f38c117f..96b2125f 100644 --- a/backend/src/dna/auth/session_store.py +++ b/backend/src/dna/auth/session_store.py @@ -80,8 +80,11 @@ class ShotGridCredentials: """ user_id: int - username: str # ShotGrid login name — never overwritten after login - access_token: str # ShotGrid Bearer token — rotated on refresh + username: str = "" # ShotGrid login name — never overwritten after login. + # Default "" for backward-compat with sessions stored + # before this field was added (they deserialise safely + # and are re-populated on the next login). + access_token: str = "" # ShotGrid Bearer token — rotated on refresh refresh_token: Optional[str] = None password: Optional[str] = None diff --git a/backend/src/dna/prodtrack_providers/shotgrid.py b/backend/src/dna/prodtrack_providers/shotgrid.py index 2afe53e8..4a913110 100644 --- a/backend/src/dna/prodtrack_providers/shotgrid.py +++ b/backend/src/dna/prodtrack_providers/shotgrid.py @@ -654,14 +654,11 @@ def get_versions_for_playlist(self, playlist_id: int) -> list[Version]: ) if version.id in notes_by_version_id: version.notes = notes_by_version_id[version.id] -<<<<<<< HEAD base = (self.url or "").rstrip("/") if base: version.prodtrack_detail_url = f"{base}/detail/Version/{version.id}" -======= ->>>>>>> 3328c7f (feat(auth): add ShotGrid PAT authentication for backend API endpoints) versions.append(version) return versions diff --git a/backend/src/main.py b/backend/src/main.py index 17b9b0c6..50a263c2 100644 --- a/backend/src/main.py +++ b/backend/src/main.py @@ -315,7 +315,7 @@ async def get_current_user( """ auth_provider_type = os.getenv("AUTH_PROVIDER", "none") if auth_provider_type == "none": - if credentials and credentials.credentials: + if credentials and credentials.credentials and auth_provider is not None: return auth_provider.get_user_email(credentials.credentials) return "anonymous@localhost" diff --git a/frontend/packages/app/src/contexts/AuthContext.tsx b/frontend/packages/app/src/contexts/AuthContext.tsx index 6325c360..ca964835 100644 --- a/frontend/packages/app/src/contexts/AuthContext.tsx +++ b/frontend/packages/app/src/contexts/AuthContext.tsx @@ -44,64 +44,59 @@ interface NoopAuthProviderInnerProps { children: ReactNode; } +// AUTH_PROVIDER=none — development/testing only. +// Uses sessionStorage so credentials are scoped to the current tab and cleared +// when the tab closes, matching the behaviour of ShotGridAuthProvider. function NoopAuthProviderInner({ children }: NoopAuthProviderInnerProps) { const [user, setUser] = useState(() => { - const stored = localStorage.getItem(USER_STORAGE_KEY); + const stored = sessionStorage.getItem(USER_STORAGE_KEY); if (stored) { try { return JSON.parse(stored); } catch { return null; } } return null; }); -const [token, setToken] = useState(() => { - return sessionStorage.getItem(STORAGE_KEY); -}); - -useEffect(() => { - if (token !== 'noop-token' || !user?.email) return; - sessionStorage.setItem(STORAGE_KEY, user.email); - setToken(user.email); -}, [token, user?.email]); - -useEffect(() => { - const authToken = - token === 'noop-token' && user?.email ? user.email : token; - if (authToken && user) { - apiHandler.setUser({ - id: user.id, - email: user.email, - name: user.name, - token: authToken, - }); - } else { - apiHandler.setUser(null); - } -}, [token, user]); + const [token, setToken] = useState(() => { + return sessionStorage.getItem(STORAGE_KEY); + }); + + useEffect(() => { + if (token !== 'noop-token' || !user?.email) return; + sessionStorage.setItem(STORAGE_KEY, user.email); + setToken(user.email); + }, [token, user?.email]); + + useEffect(() => { + const authToken = + token === 'noop-token' && user?.email ? user.email : token; + if (authToken && user) { + apiHandler.setUser({ + id: user.id, + email: user.email, + name: user.name, + token: authToken, + }); + } else { + apiHandler.setUser(null); + } + }, [token, user]); const handleSignOut = useCallback(() => { - localStorage.removeItem(STORAGE_KEY); - localStorage.removeItem(USER_STORAGE_KEY); + sessionStorage.removeItem(STORAGE_KEY); + sessionStorage.removeItem(USER_STORAGE_KEY); setToken(null); setUser(null); apiHandler.setUser(null); }, []); const handleSignInWithEmail = useCallback((email: string) => { -<<<<<<< HEAD const authUser: AuthUser = { id: email, email: email, name: email.split('@')[0], }; - - localStorage.setItem(STORAGE_KEY, email); - localStorage.setItem(USER_STORAGE_KEY, JSON.stringify(authUser)); - -======= - const authUser: AuthUser = { id: email, email, name: email.split('@')[0] }; - localStorage.setItem(STORAGE_KEY, email); - localStorage.setItem(USER_STORAGE_KEY, JSON.stringify(authUser)); ->>>>>>> 3328c7f (feat(auth): add ShotGrid PAT authentication for backend API endpoints) + sessionStorage.setItem(STORAGE_KEY, email); + sessionStorage.setItem(USER_STORAGE_KEY, JSON.stringify(authUser)); setToken(email); setUser(authUser); }, []); diff --git a/frontend/packages/app/src/contexts/ShotGridAuthContext.tsx b/frontend/packages/app/src/contexts/ShotGridAuthContext.tsx index ef7970a4..af18d294 100644 --- a/frontend/packages/app/src/contexts/ShotGridAuthContext.tsx +++ b/frontend/packages/app/src/contexts/ShotGridAuthContext.tsx @@ -72,39 +72,69 @@ export function ShotGridAuthProvider({ children }: ShotGridAuthProviderProps) { setToken(null); setUser(null); apiHandler.setUser(null); + if (refreshTimerRef.current) { + clearInterval(refreshTimerRef.current); + refreshTimerRef.current = null; + } }, []); - // ── Validate stored token + set loading false on mount ───────────────── // + // ── Validate stored token on mount ───────────────────────────────────── // + // + // Single effect handles all three cases: + // 200 OK → token valid; refresh user data from response and restore apiHandler + // 401/403 → token rejected; call clear() so login page is shown + // network error / 5xx → keep stored credentials; app will surface 401s + // naturally if the session really is dead useEffect(() => { let cancelled = false; (async () => { - // Validate stored token on mount. - // Clears the token on ANY failure — network error OR non-2xx response. - // This ensures a backend restart (which wipes MongoDB sessions) forces - // the user back to the login page instead of letting them reach the - // app with a dead session that returns 401 on every API call. const storedToken = sessionStorage.getItem(TOKEN_KEY); + const storedUserRaw = sessionStorage.getItem(USER_KEY); + const storedUser: ShotGridUser | null = storedUserRaw + ? (() => { try { return JSON.parse(storedUserRaw); } catch { return null; } })() + : null; + if (storedToken) { - // Only clear the token on a definitive rejection (401 / 403). - // Network errors (backend still starting, transient connectivity) are - // not treated as token invalidation — the user would lose their session - // every time the page is opened during a backend restart. try { const meRes = await fetch(`${apiBase}/auth/me`, { headers: { Authorization: `Bearer ${storedToken}` }, }); - const shouldClear = meRes.status === 401 || meRes.status === 403; - if (shouldClear && !cancelled) { - sessionStorage.removeItem(TOKEN_KEY); - sessionStorage.removeItem(USER_KEY); - setToken(null); - setUser(null); - apiHandler.setUser(null); + + if (cancelled) return; + + if (meRes.status === 401 || meRes.status === 403) { + // Definitive rejection — wipe the stale session. + clear(); + } else if (meRes.ok) { + // Token is valid. Parse the response to get fresh user data. + // This also covers the case where USER_KEY was missing (e.g. cleared + // by another tab) but the token is still alive. + const meData = await meRes.json().catch(() => null); + const freshUser: ShotGridUser = { + id: meData?.shotgrid_user_id ?? storedUser?.id ?? 0, + email: meData?.email ?? storedUser?.email ?? '', + name: meData?.name ?? storedUser?.name ?? '', + shotgrid_user_id: meData?.shotgrid_user_id ?? storedUser?.shotgrid_user_id, + }; + if (!cancelled) { + // Update sessionStorage and state with the freshest user data, + // then wire up apiHandler so all API calls are authenticated. + persist(storedToken, freshUser); + } + } else { + // Non-401/403 server error (e.g. 500, 503) — keep credentials; + // do not log the user out for a transient backend issue. + if (storedUser) { + apiHandler.setUser({ id: String(storedUser.id), email: storedUser.email, name: storedUser.name, token: storedToken }); + } } } catch { - // Network error — keep the stored token; the user will get a 401 - // on their first real API call if the session truly expired. + // Network error — backend unreachable. Keep stored credentials so the + // user isn't forced to log in again just because the backend is starting. + if (!cancelled && storedUser) { + apiHandler.setUser({ id: String(storedUser.id), email: storedUser.email, name: storedUser.name, token: storedToken }); + } } } @@ -153,9 +183,14 @@ export function ShotGridAuthProvider({ children }: ShotGridAuthProviderProps) { if (!res.ok) { clear(); return; } const data = await res.json(); const currentUser = sessionStorage.getItem(USER_KEY); - const parsedUser: ShotGridUser | null = currentUser ? JSON.parse(currentUser) : null; + const parsedUser: ShotGridUser | null = currentUser + ? (() => { try { return JSON.parse(currentUser); } catch { return null; } })() + : null; if (parsedUser) { persist(data.access_token, { ...parsedUser, ...data.user }); + } else { + // No stored user — can't restore; force re-login. + clear(); } } catch (err) { console.error('[ShotGridAuth] Token refresh failed:', err); @@ -168,19 +203,13 @@ export function ShotGridAuthProvider({ children }: ShotGridAuthProviderProps) { if (!token) return; refreshTimerRef.current = setInterval(refreshToken, REFRESH_INTERVAL_MS); return () => { - if (refreshTimerRef.current) clearInterval(refreshTimerRef.current); + if (refreshTimerRef.current) { + clearInterval(refreshTimerRef.current); + refreshTimerRef.current = null; + } }; }, [token, refreshToken]); - // Restore apiHandler on mount if token already in sessionStorage - useEffect(() => { - if (token && user) { - apiHandler.setUser({ id: String(user.id), email: user.email, name: user.name, token }); - } - // Run only on mount - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); - // ── Sign-out ─────────────────────────────────────────────────────────── // const signOut = useCallback(async () => { @@ -191,7 +220,7 @@ export function ShotGridAuthProvider({ children }: ShotGridAuthProviderProps) { method: 'POST', headers: { Authorization: `Bearer ${currentToken}` }, }); - } catch { /* best-effort */ } + } catch { /* best-effort — clear locally regardless */ } } clear(); }, [apiBase, clear]);