From f1efa87186c352f01118edfc2d3854e529d0e7e9 Mon Sep 17 00:00:00 2001
From: badry-dev <181844990+badry-dev@users.noreply.github.com>
Date: Sat, 22 Aug 2026 12:08:23 +0000
Subject: [PATCH 1/7] chore: v1.4 phase 0 repository hygiene
- Delete dead modules connection_service.py (fixed-salt PBKDF2 surface) and db/oracle_pool.py; verify no importers remain
- Remove all print() debug statements from runner.py and mapper.py (PII/SQL leaked to stdout); keep logger.debug equivalents
- Migrate main.py from deprecated @app.on_event('startup') to lifespan context manager
- Gate /docs and /openapi.json behind APP_ENV != production
- CI: replace committed plaintext Fernet fallback key with a per-run generated key
Co-authored-by: kiloconnect[bot] <240665456+kiloconnect[bot]@users.noreply.github.com>
---
.github/workflows/ci.yml | 9 +-
backend/app/db/oracle_pool.py | 23 --
backend/app/main.py | 31 +-
backend/app/services/connection_service.py | 354 ---------------------
backend/app/services/mapper.py | 5 -
backend/app/services/runner.py | 12 -
6 files changed, 31 insertions(+), 403 deletions(-)
delete mode 100644 backend/app/db/oracle_pool.py
delete mode 100644 backend/app/services/connection_service.py
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 7ad444b..ad419b4 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -41,11 +41,18 @@ jobs:
APP_DATABASE_URL: sqlite:///./test_app.db
APP_ENV: dev
SECRET_KEY: ${{ secrets.SECRET_KEY || 'ci-test-only-not-for-production' }}
- ENCRYPTION_KEY: ${{ secrets.ENCRYPTION_KEY || 'GRAuWlz_10CTaEXbPwOUgzBy24TZNJ5CPM2gy-kPeCA=' }}
+ # No committed fallback key: generate a throwaway Fernet key per run.
+ ENCRYPTION_KEY: ${{ secrets.ENCRYPTION_KEY }}
REDIS_URL: redis://localhost:6379/0
steps:
- uses: actions/checkout@v4
+ - name: Generate per-run Fernet key
+ if: ${{ env.ENCRYPTION_KEY == '' }}
+ run: |
+ python -c "import base64, os; print(base64.urlsafe_b64encode(os.urandom(32)).decode())" > fernet_key.txt
+ echo "ENCRYPTION_KEY=$(cat fernet_key.txt)" >> "$GITHUB_ENV"
+
- uses: actions/setup-python@v5
with:
python-version: "3.11"
diff --git a/backend/app/db/oracle_pool.py b/backend/app/db/oracle_pool.py
deleted file mode 100644
index 8bf20e0..0000000
--- a/backend/app/db/oracle_pool.py
+++ /dev/null
@@ -1,23 +0,0 @@
-import oracledb
-
-from app.core.config import settings
-
-pool: oracledb.SessionPool | None = None
-
-
-def get_pool() -> oracledb.SessionPool:
- global pool
- if pool is None:
- dsn = (
- settings.ORACLE_DSN
- or f"{settings.ORACLE_HOST}:{settings.ORACLE_PORT}/{settings.ORACLE_SERVICE_NAME}"
- )
- pool = oracledb.create_pool(
- user=settings.ORACLE_USER,
- password=settings.ORACLE_PASSWORD,
- dsn=dsn,
- min=1,
- max=5,
- increment=1,
- )
- return pool
diff --git a/backend/app/main.py b/backend/app/main.py
index fcffc44..29234cc 100644
--- a/backend/app/main.py
+++ b/backend/app/main.py
@@ -1,3 +1,5 @@
+from contextlib import asynccontextmanager
+
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
@@ -6,7 +8,25 @@
from app.core.config import settings
from app.db.session import init_app_database
-app = FastAPI(title="IntakeGateway", version="0.1.0")
+
+@asynccontextmanager
+async def lifespan(app: FastAPI):
+ init_app_database()
+ yield
+
+
+_docs_enabled = settings.APP_ENV != "production"
+
+app = FastAPI(
+ title="IntakeGateway",
+ version="0.1.0",
+ lifespan=lifespan,
+ # Interactive API docs are a development convenience; don't expose the
+ # API surface description on production deployments.
+ docs_url="/docs" if _docs_enabled else None,
+ redoc_url=None,
+ openapi_url="/openapi.json" if _docs_enabled else None,
+)
# Add CORS middleware for frontend integration
app.add_middleware(
@@ -32,11 +52,6 @@
app.include_router(oracle_router, prefix="/api/v1", tags=["oracle"])
-@app.on_event("startup")
-def startup_event():
- init_app_database()
-
-
@app.get("/health")
def health():
return {"status": "ok", "env": settings.APP_ENV}
@@ -47,6 +62,6 @@ def root():
return {
"name": app.title,
"version": app.version,
- "docs": "/docs",
- "openapi": "/openapi.json",
+ "docs": "/docs" if _docs_enabled else None,
+ "openapi": "/openapi.json" if _docs_enabled else None,
}
diff --git a/backend/app/services/connection_service.py b/backend/app/services/connection_service.py
deleted file mode 100644
index 301dc30..0000000
--- a/backend/app/services/connection_service.py
+++ /dev/null
@@ -1,354 +0,0 @@
-"""
-Connection File Service - Encrypted file storage for database connections.
-
-Uses Fernet symmetric encryption to securely store Oracle connection credentials
-in a JSON file. The encryption key is derived from the application's SECRET_KEY.
-"""
-
-import base64
-import json
-from datetime import datetime
-from pathlib import Path
-
-from cryptography.fernet import Fernet
-from cryptography.hazmat.primitives import hashes
-from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
-
-from app.core.config import settings
-
-
-class ConnectionFileService:
- """Service for managing encrypted database connection configurations."""
-
- # Connection file location (in app data directory)
- DEFAULT_CONNECTION_FILE = Path(__file__).parent.parent.parent / "data" / "connections.enc"
-
- def __init__(self, connection_file: Path | None = None):
- """
- Initialize the connection service.
-
- Args:
- connection_file: Optional custom path for the encrypted connections file.
- Defaults to backend/data/connections.enc
- """
- self.connection_file = connection_file or self.DEFAULT_CONNECTION_FILE
- self._ensure_data_directory()
- self._fernet = self._create_fernet()
-
- def _ensure_data_directory(self) -> None:
- """Create the data directory if it doesn't exist."""
- self.connection_file.parent.mkdir(parents=True, exist_ok=True)
-
- def _create_fernet(self) -> Fernet:
- """
- Create a Fernet instance using the application's SECRET_KEY.
-
- Uses PBKDF2 to derive a proper encryption key from the SECRET_KEY.
- """
- # Use a fixed salt (stored with the app) - in production, consider per-file salt
- salt = b"api2db_connection_salt_v1"
-
- kdf = PBKDF2HMAC(
- algorithm=hashes.SHA256(),
- length=32,
- salt=salt,
- iterations=480000, # OWASP recommended minimum for PBKDF2-SHA256
- )
-
- key = base64.urlsafe_b64encode(kdf.derive(settings.SECRET_KEY.encode()))
- return Fernet(key)
-
- def _read_connections(self) -> dict:
- """
- Read and decrypt connections from file.
-
- Returns:
- Dictionary of connections, empty dict if file doesn't exist.
- """
- if not self.connection_file.exists():
- return {"connections": [], "updated_at": None}
-
- try:
- encrypted_data = self.connection_file.read_bytes()
- decrypted_data = self._fernet.decrypt(encrypted_data)
- return json.loads(decrypted_data.decode("utf-8"))
- except Exception as e:
- # If decryption fails (wrong key, corrupted file), start fresh
- # Log this in production
- print(f"Warning: Could not read connections file: {e}")
- return {"connections": [], "updated_at": None}
-
- def _write_connections(self, data: dict) -> None:
- """
- Encrypt and write connections to file.
-
- Args:
- data: Dictionary containing connections list and metadata.
- """
- data["updated_at"] = datetime.utcnow().isoformat()
- json_data = json.dumps(data, indent=2)
- encrypted_data = self._fernet.encrypt(json_data.encode("utf-8"))
- self.connection_file.write_bytes(encrypted_data)
-
- def get_connection(self, name: str = "default") -> dict | None:
- """
- Get a specific connection by name.
-
- Args:
- name: Connection name (default: "default")
-
- Returns:
- Connection dictionary or None if not found.
- """
- data = self._read_connections()
- for conn in data.get("connections", []):
- if conn.get("name") == name:
- return conn
- return None
-
- def get_all_connections(self) -> list[dict]:
- """
- Get all stored connections.
-
- Returns:
- List of connection dictionaries.
- """
- data = self._read_connections()
- return data.get("connections", [])
-
- def get_active_connection(self) -> dict | None:
- """
- Get the currently active connection.
-
- Returns:
- Active connection dictionary or None if no active connection.
- """
- data = self._read_connections()
- for conn in data.get("connections", []):
- if conn.get("is_active", False):
- return conn
- # If no active connection, return the first one or None
- connections = data.get("connections", [])
- return connections[0] if connections else None
-
- def create_connection(
- self,
- name: str,
- host: str,
- port: int,
- service_name: str,
- username: str,
- password: str,
- is_active: bool = False,
- description: str | None = None,
- ) -> dict:
- """
- Create a new connection configuration.
-
- Args:
- name: Unique connection name
- host: Oracle host
- port: Oracle port
- service_name: Oracle service name
- username: Database username
- password: Database password
- is_active: Whether this is the active connection
- description: Optional description
-
- Returns:
- Created connection dictionary.
-
- Raises:
- ValueError: If connection with same name already exists.
- """
- data = self._read_connections()
-
- # Check for duplicate name
- for conn in data.get("connections", []):
- if conn.get("name") == name:
- raise ValueError(f"Connection with name '{name}' already exists")
-
- # If setting as active, deactivate others
- if is_active:
- for conn in data.get("connections", []):
- conn["is_active"] = False
-
- new_connection = {
- "name": name,
- "host": host,
- "port": port,
- "service_name": service_name,
- "username": username,
- "password": password, # Encrypted in file
- "is_active": is_active,
- "description": description,
- "created_at": datetime.utcnow().isoformat(),
- "updated_at": datetime.utcnow().isoformat(),
- }
-
- data.setdefault("connections", []).append(new_connection)
- self._write_connections(data)
-
- return new_connection
-
- def update_connection(
- self,
- name: str,
- host: str | None = None,
- port: int | None = None,
- service_name: str | None = None,
- username: str | None = None,
- password: str | None = None,
- is_active: bool | None = None,
- description: str | None = None,
- new_name: str | None = None,
- ) -> dict | None:
- """
- Update an existing connection configuration.
-
- Args:
- name: Connection name to update
- host: New Oracle host
- port: New Oracle port
- service_name: New Oracle service name
- username: New database username
- password: New database password
- is_active: Whether this is the active connection
- description: New description
- new_name: Rename the connection
-
- Returns:
- Updated connection dictionary or None if not found.
- """
- data = self._read_connections()
-
- # Check if new_name already exists (if renaming)
- if new_name:
- for conn in data.get("connections", []):
- if conn.get("name") == new_name and conn.get("name") != name:
- raise ValueError(f"Connection with name '{new_name}' already exists")
-
- updated_conn = None
- for conn in data.get("connections", []):
- if conn.get("name") == name:
- if host is not None:
- conn["host"] = host
- if port is not None:
- conn["port"] = port
- if service_name is not None:
- conn["service_name"] = service_name
- if username is not None:
- conn["username"] = username
- if password is not None:
- conn["password"] = password
- if description is not None:
- conn["description"] = description
- if new_name is not None:
- conn["name"] = new_name
- if is_active is not None:
- # If setting as active, deactivate others
- if is_active:
- for other_conn in data.get("connections", []):
- if other_conn.get("name") != conn.get("name"):
- other_conn["is_active"] = False
- conn["is_active"] = is_active
-
- conn["updated_at"] = datetime.utcnow().isoformat()
- updated_conn = conn
- break
-
- if updated_conn:
- self._write_connections(data)
-
- return updated_conn
-
- def delete_connection(self, name: str) -> bool:
- """
- Delete a connection configuration.
-
- Args:
- name: Connection name to delete
-
- Returns:
- True if deleted, False if not found.
- """
- data = self._read_connections()
- original_count = len(data.get("connections", []))
-
- data["connections"] = [
- conn for conn in data.get("connections", []) if conn.get("name") != name
- ]
-
- if len(data["connections"]) < original_count:
- self._write_connections(data)
- return True
-
- return False
-
- def set_active_connection(self, name: str) -> dict | None:
- """
- Set a connection as the active connection.
-
- Args:
- name: Connection name to activate
-
- Returns:
- Activated connection dictionary or None if not found.
- """
- return self.update_connection(name, is_active=True)
-
- def get_sqlalchemy_url(self, connection_name: str | None = None) -> str | None:
- """
- Get SQLAlchemy connection URL for a specific or the active connection.
-
- Args:
- connection_name: Optional connection name. Uses active connection if None.
-
- Returns:
- SQLAlchemy connection URL string or None if no connection found.
- """
- if connection_name:
- conn = self.get_connection(connection_name)
- else:
- conn = self.get_active_connection()
-
- if not conn:
- return None
-
- return (
- f"oracle+oracledb://{conn['username']}:{conn['password']}"
- f"@{conn['host']}:{conn['port']}/?service_name={conn['service_name']}"
- )
-
- def connection_exists(self, name: str) -> bool:
- """Check if a connection with the given name exists."""
- return self.get_connection(name) is not None
-
- def get_connection_without_password(self, name: str) -> dict | None:
- """
- Get a connection without the password field (for API responses).
-
- Args:
- name: Connection name
-
- Returns:
- Connection dictionary without password or None if not found.
- """
- conn = self.get_connection(name)
- if conn:
- return {k: v for k, v in conn.items() if k != "password"}
- return None
-
- def get_all_connections_without_passwords(self) -> list[dict]:
- """
- Get all connections without password fields (for API responses).
-
- Returns:
- List of connection dictionaries without passwords.
- """
- connections = self.get_all_connections()
- return [{k: v for k, v in conn.items() if k != "password"} for conn in connections]
-
-
-# Singleton instance for use across the application
-connection_service = ConnectionFileService()
diff --git a/backend/app/services/mapper.py b/backend/app/services/mapper.py
index fa1f1ac..16da899 100644
--- a/backend/app/services/mapper.py
+++ b/backend/app/services/mapper.py
@@ -112,18 +112,14 @@ def to_timestamp(x):
def to_date(x):
"""Convert common date/time strings to Oracle DATE format (YYYY-MM-DD)."""
- print(f"=== DEBUG to_date called with: {repr(x)} (type: {type(x).__name__})")
if x is None or x == "":
- print("=== DEBUG to_date returning None (empty input)")
return None
try:
if isinstance(x, str):
- print(f"=== DEBUG to_date parsing string: '{x}'")
logger.debug(f"Parsing date string: '{x}'")
dt = _parse_datetime_value(x)
result = dt.strftime("%Y-%m-%d")
- print(f"=== DEBUG to_date successfully parsed to: '{result}'")
logger.debug(f"Successfully parsed '{x}' to '{result}'")
return result
elif isinstance(x, datetime):
@@ -131,7 +127,6 @@ def to_date(x):
else:
raise ValueError(f"Cannot convert {type(x).__name__} to date")
except Exception as e:
- print(f"=== DEBUG to_date ERROR: {e}")
logger.error(
f"Error converting to date: value='{x}' type={type(x).__name__} error={str(e)}"
)
diff --git a/backend/app/services/runner.py b/backend/app/services/runner.py
index eaf3ce2..9a6dd76 100644
--- a/backend/app/services/runner.py
+++ b/backend/app/services/runner.py
@@ -268,14 +268,6 @@ async def run_import(
# Debug: Log source field names and sample values
if flattened_records:
sample_record = flattened_records[0]
- print(f"\n=== DEBUG: Sample flattened record keys: {list(sample_record.keys())}")
- print(f"=== DEBUG: Sample flattened record: {sample_record}")
- for mapping in column_mappings:
- source_val = sample_record.get(mapping.source_field)
- print(
- f"=== DEBUG: Mapping: {mapping.source_field} -> {mapping.dest_column} "
- f"| transforms={mapping.transform_rules} | source_value={repr(source_val)}"
- )
logger.debug(f"Sample flattened record keys: {list(sample_record.keys())}")
logger.debug(f"Sample flattened record: {sample_record}")
for mapping in column_mappings:
@@ -290,7 +282,6 @@ async def run_import(
# Debug: Log mapped results
if mapped_records:
sample_mapped = mapped_records[0]
- print(f"=== DEBUG: Sample mapped record: {sample_mapped}")
logger.debug(f"Sample mapped record: {sample_mapped}")
# Step 7: Validate rows
@@ -545,7 +536,6 @@ def _build_insert_statement(
# Debug: Log sample row
if sample_row:
logger.debug(f"Sample row for date detection: {sample_row}")
- print(f"=== DEBUG: Sample row for date detection: {sample_row}")
# Build placeholders, wrapping date strings with TO_DATE()
placeholders = []
@@ -559,7 +549,6 @@ def _build_insert_statement(
if isinstance(value, str) and re.match(r"^\d{4}-\d{2}-\d{2}$", value):
is_date_string = True
logger.debug(f"Detected date column: {column} = {value}")
- print(f"=== DEBUG: Detected date column: {column} = {value}")
if is_date_string:
# Wrap with TO_DATE() for Oracle DATE columns
@@ -571,7 +560,6 @@ def _build_insert_statement(
# Debug: Log generated SQL
logger.debug(f"Generated INSERT SQL: {insert_sql}")
- print(f"=== DEBUG: Generated INSERT SQL: {insert_sql}")
return insert_sql, columns, dict(zip(columns, bind_names, strict=True))
From f140cbbb033c096711d73fa3cb973987a5191d38 Mon Sep 17 00:00:00 2001
From: badry-dev <181844990+badry-dev@users.noreply.github.com>
Date: Sat, 22 Aug 2026 12:24:56 +0000
Subject: [PATCH 2/7] =?UTF-8?q?fix:=20v1.4=20phase=201=20=E2=80=94=20criti?=
=?UTF-8?q?cal=20correctness=20(scheduler=20dispatch,=20bulk=20NULL=20corr?=
=?UTF-8?q?uption,=20batch=20skip=20condition)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
- C1 scheduler: _execute_scheduled_task called enqueue_run.delay() but enqueue_run is a plain function -> AttributeError on every cron fire, silently swallowed. Now calls enqueue_run(task_id). On dispatch failure, TaskSchedule.consecutive_failures is incremented and committed in a separate transaction AFTER the rollback; reset to 0 on success. Adds model column + Alembic migration 004.
- C2 bulk CASE-UPDATE: add ELSE
to every CASE so rows with None values preserve the stored value instead of writing NULL (ORA-01407 regression); update_cols is now a first-seen union across ALL rows instead of first-row only, so columns present only in later rows are still written.
- C3 batch skip condition: skip_column included in the batch SELECT; existing rows matching the skip condition are routed to to_skip and counted in results['skipped']/TaskRun.rows_skipped instead of being overwritten. Existing-key lookup now tolerates case-varying column names from the driver.
- Tests: new test_scheduler.py (dispatch invokes run_import_task.delay via enqueue_run; failure counter persists after rollback); new NULL-preservation + heterogeneous-batch + batch-skip regression tests executing real SQL against in-memory SQLite; stale per-row-path tests rewritten against the batch implementation.
Co-authored-by: kiloconnect[bot] <240665456+kiloconnect[bot]@users.noreply.github.com>
---
.../004_schedule_consecutive_failures.py | 35 ++
backend/app/db/models/task_schedule.py | 6 +-
backend/app/services/runner.py | 79 ++--
backend/app/services/scheduler.py | 29 +-
backend/tests/unit/test_scheduler.py | 109 +++++
backend/tests/unit/test_upsert_logic.py | 375 +++++++++++-------
6 files changed, 456 insertions(+), 177 deletions(-)
create mode 100644 backend/alembic/versions/004_schedule_consecutive_failures.py
create mode 100644 backend/tests/unit/test_scheduler.py
diff --git a/backend/alembic/versions/004_schedule_consecutive_failures.py b/backend/alembic/versions/004_schedule_consecutive_failures.py
new file mode 100644
index 0000000..1ded4d0
--- /dev/null
+++ b/backend/alembic/versions/004_schedule_consecutive_failures.py
@@ -0,0 +1,35 @@
+"""Add consecutive_failures counter to task_schedule
+
+Revision ID: 004_schedule_consecutive_failures
+Revises: 003_p0_oauth_ratelimit_cursor
+Create Date: 2026-08-22 00:00:00.000000
+
+Tracks consecutive scheduler dispatch failures per schedule so operators can
+detect tasks whose cron jobs repeatedly fail to enqueue. Reset to 0 on each
+successful dispatch.
+"""
+from alembic import op
+import sqlalchemy as sa
+
+
+revision = '004_schedule_consecutive_failures'
+down_revision = '003_p0_oauth_ratelimit_cursor'
+branch_labels = None
+depends_on = None
+
+
+def upgrade() -> None:
+ with op.batch_alter_table('task_schedule', schema=None) as batch_op:
+ batch_op.add_column(
+ sa.Column(
+ 'consecutive_failures',
+ sa.Integer(),
+ nullable=False,
+ server_default=sa.text('0'),
+ )
+ )
+
+
+def downgrade() -> None:
+ with op.batch_alter_table('task_schedule', schema=None) as batch_op:
+ batch_op.drop_column('consecutive_failures')
diff --git a/backend/app/db/models/task_schedule.py b/backend/app/db/models/task_schedule.py
index 92f64d8..d6fbe73 100644
--- a/backend/app/db/models/task_schedule.py
+++ b/backend/app/db/models/task_schedule.py
@@ -1,4 +1,4 @@
-from sqlalchemy import Boolean, Column, DateTime, ForeignKey, String
+from sqlalchemy import Boolean, Column, DateTime, ForeignKey, Integer, String
from sqlalchemy.sql import func
from app.db.session import Base
@@ -14,5 +14,9 @@ class TaskSchedule(Base):
is_active = Column(Boolean, nullable=False, default=True, index=True)
last_run_date = Column(DateTime(timezone=True), nullable=True)
next_run_date = Column(DateTime(timezone=True), nullable=True)
+ # Number of consecutive dispatch failures for this schedule; reset to 0 on
+ # a successful enqueue. Persisted in its own transaction after rollback so
+ # the counter survives the error path.
+ consecutive_failures = Column(Integer, nullable=False, server_default="0", default=0)
created_at = Column(DateTime(timezone=True), server_default=func.now())
updated_at = Column(DateTime(timezone=True), onupdate=func.now())
diff --git a/backend/app/services/runner.py b/backend/app/services/runner.py
index 9a6dd76..2a61dcf 100644
--- a/backend/app/services/runner.py
+++ b/backend/app/services/runner.py
@@ -747,17 +747,30 @@ def _process_upsert_batch(
for key in upsert_keys:
params[f"{key}_{id(row)}"] = row.get(key)
- select_sql = f"SELECT {', '.join(_quote_column_name(k) for k in upsert_keys)} FROM {_format_table_name(table_name)} WHERE {where_sql}"
+ # Include the skip column in the projection when a skip condition is
+ # configured so matching rows can be routed to `to_skip` without a
+ # second per-row query.
+ select_columns = list(upsert_keys)
+ if task.skip_column and task.skip_value:
+ skip_col = _clean_identifier(task.skip_column)
+ if skip_col not in select_columns:
+ select_columns.append(skip_col)
+
+ select_sql = f"SELECT {', '.join(_quote_column_name(k) for k in select_columns)} FROM {_format_table_name(table_name)} WHERE {where_sql}"
existing_result = db.execute(text(select_sql), params).fetchall()
- # Build set of existing record keys for fast lookup
- existing_keys = set()
+ # Build map of existing record keys -> fetched column values for fast lookup
+ existing_rows = {}
for row in existing_result:
- key_tuple = tuple(row[i] for i in range(len(upsert_keys)))
- existing_keys.add(key_tuple)
+ mapping = dict(row._mapping)
+ key_tuple = tuple(
+ mapping.get(key, mapping.get(key.upper(), mapping.get(key.lower())))
+ for key in upsert_keys
+ )
+ existing_rows[key_tuple] = mapping
- logger.debug(f"Found {len(existing_keys)} existing records out of {len(batch)}")
+ logger.debug(f"Found {len(existing_rows)} existing records out of {len(batch)}")
# Step 2: Split batch into insert/update/skip lists
to_insert = []
@@ -766,19 +779,18 @@ def _process_upsert_batch(
for idx, row in enumerate(batch):
row_key_tuple = tuple(row.get(key) for key in upsert_keys)
+ existing = existing_rows.get(row_key_tuple)
- if row_key_tuple in existing_keys:
- # Record exists - check skip condition
- if task.skip_column and task.skip_value:
- # Need to fetch the full record to check skip value
- # For performance, we skip this check in batch mode
- # or we can do a second SELECT for skip checks
- pass # TODO: implement skip check if needed
-
- to_update.append((batch_offset + idx, row))
- else:
+ if existing is None:
# New record
to_insert.append((batch_offset + idx, row))
+ elif task.skip_column and task.skip_value and _should_skip(task, existing):
+ # Record exists and matches the configured skip condition —
+ # third parties may have marked this row processed; never
+ # overwrite it.
+ to_skip.append((batch_offset + idx, row))
+ else:
+ to_update.append((batch_offset + idx, row))
# Step 3: Bulk UPDATE (if any)
if to_update:
@@ -823,9 +835,14 @@ def _bulk_update_rows(
WHEN key1=val1 THEN newval1
WHEN key1=val2 THEN newval2
...
+ ELSE col1
END,
- col2 = CASE ...
+ col2 = CASE ... ELSE col2 END
WHERE (key1=val1) OR (key1=val2) OR ...
+
+ The `ELSE ` branch preserves the existing value for rows whose
+ incoming value is None — without it the CASE evaluates to NULL and would
+ overwrite NOT NULL columns, reintroducing ORA-01407.
"""
import re
@@ -833,14 +850,18 @@ def _bulk_update_rows(
return 0
table_name = task.dest_table
- sample_row = rows_with_idx[0][1]
- # Get columns to update (exclude upsert keys and None values)
- update_cols = [
- col
- for col in sample_row.keys()
- if col not in upsert_keys and sample_row.get(col) is not None
- ]
+ # Columns to update: deterministic first-seen union of non-key columns
+ # across ALL rows. Deriving them from the first row only would silently
+ # drop columns that happen to be absent/None in row 1 but present in later
+ # rows.
+ update_cols = []
+ seen_cols = set()
+ for _, row in rows_with_idx:
+ for col in row.keys():
+ if col not in upsert_keys and col not in seen_cols:
+ seen_cols.add(col)
+ update_cols.append(col)
if not update_cols:
return 0 # Nothing to update
@@ -856,7 +877,8 @@ def _bulk_update_rows(
# Get the value for this column
value = row.get(col)
- # Skip this row if value is None (don't add WHEN clause to avoid setting NULL)
+ # Rows with a None value get no WHEN clause; the CASE's ELSE
+ # branch below preserves the stored value instead of writing NULL.
if value is None:
continue
@@ -883,7 +905,12 @@ def _bulk_update_rows(
# Only add this column to SET clause if at least one row has a non-None value
if case_whens:
- case_sql = f"{_quote_column_name(col)} = CASE {' '.join(case_whens)} END"
+ # ELSE keeps the existing column value for any row in the WHERE
+ # scope whose incoming value for this column is None.
+ case_sql = (
+ f"{_quote_column_name(col)} = CASE {' '.join(case_whens)} "
+ f"ELSE {_quote_column_name(col)} END"
+ )
set_clauses.append(case_sql)
# If no columns to update (all rows had None values for all columns), skip UPDATE
diff --git a/backend/app/services/scheduler.py b/backend/app/services/scheduler.py
index ce86b58..2125ccb 100644
--- a/backend/app/services/scheduler.py
+++ b/backend/app/services/scheduler.py
@@ -115,8 +115,11 @@ def _execute_scheduled_task(self, task_id: int, task_name: str):
try:
logger.info(f"Triggering scheduled import for task {task_id} '{task_name}'")
- # Enqueue task to Celery
- result = enqueue_run.delay(task_id)
+ # Enqueue task to Celery. enqueue_run() itself delegates to
+ # run_import_task.delay(); calling .delay on it again would raise
+ # AttributeError on every cron fire (the function is not a Celery
+ # task itself).
+ result = enqueue_run(task_id)
# Update last_run_date and next_run_date
task_schedule = (
@@ -125,6 +128,7 @@ def _execute_scheduled_task(self, task_id: int, task_name: str):
if task_schedule:
task_schedule.last_run_date = datetime.now(UTC)
+ task_schedule.consecutive_failures = 0
# Calculate next run
next_run = croniter(task_schedule.cron_expression, datetime.now(UTC)).get_next(
@@ -142,6 +146,27 @@ def _execute_scheduled_task(self, task_id: int, task_name: str):
logger.error(f"Failed to execute scheduled task {task_id}: {str(e)}")
self.db.rollback()
+ # Persist the failure counter in a separate transaction: the
+ # rollback above discards anything staged before the error, so the
+ # increment must happen after it.
+ try:
+ task_schedule = (
+ self.db.query(TaskSchedule).filter(TaskSchedule.task_id == task_id).first()
+ )
+ if task_schedule:
+ current = task_schedule.consecutive_failures or 0
+ task_schedule.consecutive_failures = current + 1
+ self.db.commit()
+ logger.error(
+ f"Scheduled task {task_id} dispatch failure "
+ f"(consecutive_failures={current + 1})"
+ )
+ except Exception as counter_exc:
+ logger.error(
+ f"Failed to persist consecutive_failures for task {task_id}: {counter_exc}"
+ )
+ self.db.rollback()
+
def reload_schedules(self):
"""Reload all schedules from database (useful after schedule updates)"""
logger.info("Reloading all task schedules...")
diff --git a/backend/tests/unit/test_scheduler.py b/backend/tests/unit/test_scheduler.py
new file mode 100644
index 0000000..055ba93
--- /dev/null
+++ b/backend/tests/unit/test_scheduler.py
@@ -0,0 +1,109 @@
+"""Regression tests for scheduler dispatch (v1.4 C1).
+
+The scheduled-job handler used to call ``enqueue_run.delay(task_id)``, but
+``enqueue_run`` is a plain function that already delegates to
+``run_import_task.delay()`` — so every cron fire raised AttributeError,
+swallowed by the broad except. These tests pin the corrected dispatch and the
+consecutive-failure counter behavior.
+"""
+
+from unittest.mock import MagicMock, patch
+
+import pytest
+
+from app.db.models.task_schedule import TaskSchedule
+from app.services.scheduler import TaskScheduler
+
+
+@pytest.fixture
+def scheduler():
+ with patch("app.services.scheduler.SessionLocal") as mock_session_local:
+ db = MagicMock()
+ mock_session_local.return_value = db
+ sched = TaskScheduler()
+ sched.db = db
+ yield sched, db
+
+
+def _make_schedule(consecutive_failures=0):
+ schedule = MagicMock(spec=TaskSchedule)
+ schedule.task_id = 7
+ schedule.cron_expression = "0 2 * * *"
+ schedule.consecutive_failures = consecutive_failures
+ return schedule
+
+
+class TestScheduledDispatch:
+ def test_dispatch_invokes_enqueue_run(self, scheduler):
+ """The cron fire must call enqueue_run(task_id) — which itself invokes
+ run_import_task.delay — not enqueue_run.delay (AttributeError)."""
+ sched, db = scheduler
+
+ schedule = _make_schedule()
+ db.query.return_value.filter.return_value.first.return_value = schedule
+
+ with patch("app.services.scheduler.enqueue_run") as mock_enqueue:
+ mock_enqueue.return_value = MagicMock(id="celery-id-1")
+ sched._execute_scheduled_task(7, "Nightly import")
+
+ mock_enqueue.assert_called_once_with(7)
+ assert schedule.consecutive_failures == 0
+
+ def test_dispatch_updates_schedule_dates_on_success(self, scheduler):
+ sched, db = scheduler
+
+ schedule = _make_schedule()
+ db.query.return_value.filter.return_value.first.return_value = schedule
+
+ with patch("app.services.scheduler.enqueue_run") as mock_enqueue:
+ mock_enqueue.return_value = MagicMock(id="celery-id-2")
+ sched._execute_scheduled_task(7, "Nightly import")
+
+ assert schedule.last_run_date is not None
+ assert schedule.next_run_date is not None
+ db.commit.assert_called()
+
+
+class TestDispatchFailureCounter:
+ def test_failure_counter_persists_when_enqueue_raises(self, scheduler):
+ """On enqueue failure the counter is incremented and committed in a
+ separate transaction AFTER the rollback."""
+ sched, db = scheduler
+
+ schedule = _make_schedule(consecutive_failures=2)
+ db.query.return_value.filter.return_value.first.return_value = schedule
+
+ with patch("app.services.scheduler.enqueue_run", side_effect=RuntimeError("broker down")):
+ sched._execute_scheduled_task(7, "Nightly import")
+
+ # Rollback happens first (discarding pre-error state)...
+ db.rollback.assert_called_once()
+ # ...then the counter is incremented in a fresh transaction.
+ assert schedule.consecutive_failures == 3
+ assert db.commit.call_count >= 1
+
+ def test_failure_without_schedule_row_does_not_crash(self, scheduler):
+ """No matching TaskSchedule row -> counter step is skipped safely."""
+ sched, db = scheduler
+ db.query.return_value.filter.return_value.first.return_value = None
+
+ with patch("app.services.scheduler.enqueue_run", side_effect=RuntimeError("broker down")):
+ sched._execute_scheduled_task(7, "Nightly import")
+
+ db.rollback.assert_called()
+
+ def test_counter_commit_failure_is_swallowed(self, scheduler):
+ """A failure while persisting the counter must not escape the handler
+ (it would make APScheduler mark the job as errored)."""
+ sched, db = scheduler
+ schedule = _make_schedule(consecutive_failures=1)
+ db.query.return_value.filter.return_value.first.return_value = schedule
+ db.commit.side_effect = RuntimeError("db gone")
+
+ with patch("app.services.scheduler.enqueue_run", side_effect=RuntimeError("broker down")):
+ # Must not raise
+ sched._execute_scheduled_task(7, "Nightly import")
+
+
+if __name__ == "__main__":
+ pytest.main([__file__, "-v"])
diff --git a/backend/tests/unit/test_upsert_logic.py b/backend/tests/unit/test_upsert_logic.py
index db56a87..3a600db 100644
--- a/backend/tests/unit/test_upsert_logic.py
+++ b/backend/tests/unit/test_upsert_logic.py
@@ -9,6 +9,7 @@
from app.services.runner import (
RowResult,
RowStatus,
+ _bulk_update_rows,
_get_record_key,
_process_single_row,
_should_skip,
@@ -260,20 +261,22 @@ def test_error_on_database_error(self, mock_find, mock_insert, mock_db, mock_tas
class TestProcessRowsWithUpsert:
- """Tests for process_rows_with_upsert function"""
+ """Tests for the batched process_rows_with_upsert implementation"""
- @patch("app.services.runner._process_single_row")
- @patch("app.services.runner.log_row_error")
- def test_process_all_inserted(self, mock_log_error, mock_process, mock_db, mock_task_upsert):
+ @patch("app.services.runner._process_upsert_batch")
+ def test_process_all_inserted(self, mock_batch, mock_db, mock_task_upsert):
"""Test all rows inserted successfully"""
rows = [
{"employee_id": 1, "name": "Alice"},
{"employee_id": 2, "name": "Bob"},
]
- mock_process.side_effect = [
- RowResult(status=RowStatus.INSERTED, record_key="employee_id=1"),
- RowResult(status=RowStatus.INSERTED, record_key="employee_id=2"),
- ]
+ mock_batch.return_value = {
+ "inserted": 2,
+ "updated": 0,
+ "skipped": 0,
+ "errors": 0,
+ "error_details": [],
+ }
results = process_rows_with_upsert(mock_db, mock_task_upsert, 1, rows)
@@ -281,28 +284,23 @@ def test_process_all_inserted(self, mock_log_error, mock_process, mock_db, mock_
assert results["updated"] == 0
assert results["skipped"] == 0
assert results["errors"] == 0
- mock_log_error.assert_not_called()
+ assert mock_batch.call_count == 1
- @patch("app.services.runner._process_single_row")
- @patch("app.services.runner.log_row_error")
- def test_process_mixed_results(
- self, mock_log_error, mock_process, mock_db, mock_task_upsert_with_skip
- ):
- """Test mixed results: insert, update, skip"""
+ @patch("app.services.runner._process_upsert_batch")
+ def test_process_mixed_results(self, mock_batch, mock_db, mock_task_upsert_with_skip):
+ """Test mixed results: insert, update, skip are aggregated across batches"""
rows = [
{"employee_id": 1, "name": "Alice"},
{"employee_id": 2, "name": "Bob"},
{"employee_id": 3, "name": "Charlie"},
]
- mock_process.side_effect = [
- RowResult(status=RowStatus.INSERTED, record_key="employee_id=1"),
- RowResult(status=RowStatus.UPDATED, record_key="employee_id=2"),
- RowResult(
- status=RowStatus.SKIPPED,
- record_key="employee_id=3",
- message="Already processed",
- ),
- ]
+ mock_batch.return_value = {
+ "inserted": 1,
+ "updated": 1,
+ "skipped": 1,
+ "errors": 0,
+ "error_details": [],
+ }
results = process_rows_with_upsert(mock_db, mock_task_upsert_with_skip, 1, rows)
@@ -311,52 +309,39 @@ def test_process_mixed_results(
assert results["skipped"] == 1
assert results["errors"] == 0
- @patch("app.services.runner._process_single_row")
- @patch("app.services.runner.log_row_error")
- def test_continue_on_error(self, mock_log_error, mock_process, mock_db, mock_task_upsert):
- """Test processing continues when error occurs (continue_on_error=True)"""
+ @patch("app.services.runner._process_upsert_batch")
+ def test_continue_on_error(self, mock_batch, mock_db, mock_task_upsert):
+ """Test processing continues when a batch errors (continue_on_error=True)"""
rows = [
- {"employee_id": 1, "name": "Alice"},
- {"employee_id": 2, "name": "Bob"},
- {"employee_id": 3, "name": "Charlie"},
- ]
- mock_process.side_effect = [
- RowResult(status=RowStatus.INSERTED, record_key="employee_id=1"),
- RowResult(
- status=RowStatus.ERROR,
- record_key="employee_id=2",
- message="Constraint error",
- ),
- RowResult(status=RowStatus.INSERTED, record_key="employee_id=3"),
+ {"employee_id": i, "name": f"Person {i}"} for i in range(1, 1001)
+ ] # two batches of 500
+ mock_batch.side_effect = [
+ {
+ "inserted": 500,
+ "updated": 0,
+ "skipped": 0,
+ "errors": 0,
+ "error_details": [],
+ },
+ {
+ "inserted": 400,
+ "updated": 0,
+ "skipped": 0,
+ "errors": 100,
+ "error_details": [{"batch_start": 500, "batch_size": 500, "error": "boom"}],
+ },
]
results = process_rows_with_upsert(mock_db, mock_task_upsert, 1, rows)
- # All rows should be processed despite error
- assert results["inserted"] == 2
- assert results["errors"] == 1
+ # Second batch failure must not abort aggregation
+ assert results["inserted"] == 900
+ assert results["errors"] == 100
assert len(results["error_details"]) == 1
- assert results["error_details"][0]["row_index"] == 1
- mock_log_error.assert_called_once()
-
- @patch("app.services.runner._process_single_row")
- @patch("app.services.runner.log_row_error")
- def test_stop_on_error_when_disabled(
- self, mock_log_error, mock_process, mock_db, mock_task_no_continue
- ):
- """Test processing stops when error occurs and continue_on_error=False"""
- rows = [
- {"employee_id": 1, "name": "Alice"},
- {"employee_id": 2, "name": "Bob"},
- ]
- mock_process.side_effect = Exception("Unexpected error")
+ assert mock_batch.call_count == 2
- with pytest.raises(Exception, match="Unexpected error"):
- process_rows_with_upsert(mock_db, mock_task_no_continue, 1, rows)
-
- @patch("app.services.runner._process_single_row")
- @patch("app.services.runner.log_row_error")
- def test_empty_rows(self, mock_log_error, mock_process, mock_db, mock_task_upsert):
+ @patch("app.services.runner._process_upsert_batch")
+ def test_empty_rows(self, mock_batch, mock_db, mock_task_upsert):
"""Test processing empty rows list"""
results = process_rows_with_upsert(mock_db, mock_task_upsert, 1, [])
@@ -364,106 +349,200 @@ def test_empty_rows(self, mock_log_error, mock_process, mock_db, mock_task_upser
assert results["updated"] == 0
assert results["skipped"] == 0
assert results["errors"] == 0
- mock_process.assert_not_called()
+ mock_batch.assert_not_called()
- @patch("app.services.runner._process_single_row")
- @patch("app.services.runner.log_row_error")
- def test_all_rows_skipped(
- self, mock_log_error, mock_process, mock_db, mock_task_upsert_with_skip
- ):
- """Test all rows skipped due to skip condition"""
- rows = [
- {"employee_id": 1, "name": "Alice"},
- {"employee_id": 2, "name": "Bob"},
- ]
- mock_process.side_effect = [
- RowResult(
- status=RowStatus.SKIPPED,
- record_key="employee_id=1",
- message="processed=Y",
- ),
- RowResult(
- status=RowStatus.SKIPPED,
- record_key="employee_id=2",
- message="processed=Y",
- ),
- ]
+ @patch("app.services.runner._process_upsert_batch")
+ def test_no_upsert_keys_falls_back_to_insert(self, mock_batch, mock_db, mock_task_insert_only):
+ """No upsert keys configured -> bulk insert path, no batch upsert"""
+ rows = [{"employee_id": 1, "name": "Alice"}]
- results = process_rows_with_upsert(mock_db, mock_task_upsert_with_skip, 1, rows)
+ with patch("app.services.runner.insert_batch", return_value=1) as mock_insert:
+ results = process_rows_with_upsert(mock_db, mock_task_insert_only, 1, rows)
- assert results["inserted"] == 0
- assert results["updated"] == 0
- assert results["skipped"] == 2
- assert results["errors"] == 0
+ assert results["inserted"] == 1
+ mock_insert.assert_called_once()
+ mock_batch.assert_not_called()
- @patch("app.services.runner._process_single_row")
- @patch("app.services.runner.log_row_error")
- def test_all_rows_error(self, mock_log_error, mock_process, mock_db, mock_task_upsert):
- """Test all rows have errors but processing continues"""
- rows = [
- {"employee_id": 1, "name": "Alice"},
- {"employee_id": 2, "name": "Bob"},
- ]
- mock_process.side_effect = [
- RowResult(status=RowStatus.ERROR, record_key="employee_id=1", message="Error 1"),
- RowResult(status=RowStatus.ERROR, record_key="employee_id=2", message="Error 2"),
- ]
- results = process_rows_with_upsert(mock_db, mock_task_upsert, 1, rows)
+class TestBulkUpdateNullPreservation:
+ """Regression tests for the bulk CASE-UPDATE NULL corruption (v1.4 C2).
- assert results["inserted"] == 0
- assert results["updated"] == 0
- assert results["errors"] == 2
- assert len(results["error_details"]) == 2
- assert mock_log_error.call_count == 2
+ These run real SQL against an in-memory SQLite destination. The generated
+ statements use double-quoted identifiers and plain binds (no TO_DATE unless
+ a YYYY-MM-DD string is present), so they execute on SQLite.
+ """
+ @pytest.fixture
+ def sqlite_dest(self):
+ from sqlalchemy import create_engine, text
+ from sqlalchemy.orm import sessionmaker
-class TestUpsertIntegration:
- """Integration tests for upsert flow"""
+ engine = create_engine("sqlite:///:memory:")
+ conn = engine.connect()
+ conn.execute(
+ text('CREATE TABLE "EMPLOYEES" ("employee_id" INTEGER PRIMARY KEY, "name" TEXT)')
+ )
+ conn.commit()
+ Session = sessionmaker(bind=engine)
+ yield Session()
+ conn.close()
+ engine.dispose()
+
+ @pytest.fixture
+ def task(self):
+ task = MagicMock(spec=Task)
+ task.id = 1
+ task.dest_table = "EMPLOYEES"
+ task.upsert_enabled = True
+ task.upsert_keys = ["employee_id"]
+ task.skip_column = None
+ task.skip_value = None
+ return task
+
+ def _seed(self, db, employee_id, name):
+ from sqlalchemy import text
+
+ db.execute(
+ text('INSERT INTO "EMPLOYEES" ("employee_id", "name") VALUES (:i, :n)'),
+ {"i": employee_id, "n": name},
+ )
+ db.commit()
- @patch("app.services.runner._find_existing_record")
- @patch("app.services.runner._insert_single_row")
- @patch("app.services.runner._update_existing_row")
- @patch("app.services.runner.log_row_error")
- def test_full_upsert_flow(
- self,
- mock_log_error,
- mock_update,
- mock_insert,
- mock_find,
- mock_db,
- mock_task_upsert_with_skip,
- ):
- """Test complete upsert flow with mixed scenarios"""
- rows = [
- {"employee_id": 1, "name": "New Employee"}, # Will INSERT
- {"employee_id": 2, "name": "Updated Employee"}, # Will UPDATE
- {"employee_id": 3, "name": "Processed Employee"}, # Will SKIP
+ def _fetch_name(self, db, employee_id):
+ from sqlalchemy import text
+
+ result = db.execute(
+ text('SELECT "name" FROM "EMPLOYEES" WHERE "employee_id" = :i'),
+ {"i": employee_id},
+ ).scalar()
+ return result
+
+ def test_none_value_preserves_existing_column(self, sqlite_dest, task):
+ """A None in the incoming row must NOT overwrite the stored value."""
+ self._seed(sqlite_dest, 1, "Original")
+
+ to_update = [(0, {"employee_id": 1, "name": None})]
+ count = _bulk_update_rows(sqlite_dest, task, to_update, ["employee_id"])
+
+ # Every value for every column is None -> no SET clauses are emitted
+ # (count 0), which trivially preserves the stored value.
+ assert count == 0
+ assert self._fetch_name(sqlite_dest, 1) == "Original"
+
+ def test_non_none_value_is_written(self, sqlite_dest, task):
+ self._seed(sqlite_dest, 1, "Original")
+
+ to_update = [(0, {"employee_id": 1, "name": "Updated"})]
+ _bulk_update_rows(sqlite_dest, task, to_update, ["employee_id"])
+
+ assert self._fetch_name(sqlite_dest, 1) == "Updated"
+
+ def test_heterogeneous_batch_mixed_nulls(self, sqlite_dest, task):
+ """Mixed batch: non-None values written, None values preserved."""
+ self._seed(sqlite_dest, 1, "Keep-Me")
+ self._seed(sqlite_dest, 2, "Old-Name")
+
+ to_update = [
+ (0, {"employee_id": 1, "name": "New-Name"}),
+ (1, {"employee_id": 2, "name": None}),
]
+ _bulk_update_rows(sqlite_dest, task, to_update, ["employee_id"])
- # Mock find results
- mock_find.side_effect = [
- None, # Row 1: not found -> INSERT
- {
- "employee_id": 2,
- "name": "Old",
- "processed": "N",
- }, # Row 2: found, not processed -> UPDATE
- {
- "employee_id": 3,
- "name": "Old",
- "processed": "Y",
- }, # Row 3: found, processed -> SKIP
+ assert self._fetch_name(sqlite_dest, 1) == "New-Name"
+ assert self._fetch_name(sqlite_dest, 2) == "Old-Name"
+
+ def test_column_present_only_in_later_row_is_updated(self, sqlite_dest, task):
+ """update_cols must be a union across all rows, not first-row only.
+
+ Row 1 lacks `name` entirely; row 2 has it. Both must be handled.
+ """
+ from sqlalchemy import text
+
+ sqlite_dest.execute(text('ALTER TABLE "EMPLOYEES" ADD COLUMN "email" TEXT'))
+ self._seed(sqlite_dest, 1, "Row One")
+ self._seed(sqlite_dest, 2, "Row Two")
+
+ to_update = [
+ (0, {"employee_id": 1}), # no name/email keys at all
+ (1, {"employee_id": 2, "email": "row2@example.com"}),
]
+ count = _bulk_update_rows(sqlite_dest, task, to_update, ["employee_id"])
+
+ assert count >= 1
+ email_val = sqlite_dest.execute(
+ text('SELECT "email" FROM "EMPLOYEES" WHERE "employee_id" = :i'), {"i": 2}
+ ).scalar()
+ assert email_val == "row2@example.com"
+
+
+class TestBatchSkipCondition:
+ """Regression tests for the unimplemented skip condition in the batch
+ upsert path (v1.4 C3)."""
+
+ @pytest.fixture
+ def sqlite_dest(self):
+ from sqlalchemy import create_engine, text
+ from sqlalchemy.orm import sessionmaker
+
+ engine = create_engine("sqlite:///:memory:")
+ conn = engine.connect()
+ conn.execute(
+ text(
+ 'CREATE TABLE "EMPLOYEES" ('
+ '"employee_id" INTEGER PRIMARY KEY, "name" TEXT, "processed" TEXT)'
+ )
+ )
+ conn.commit()
+ Session = sessionmaker(bind=engine)
+ yield Session()
+ conn.close()
+ engine.dispose()
+
+ @pytest.fixture
+ def task_with_skip(self):
+ task = MagicMock(spec=Task)
+ task.id = 1
+ task.dest_table = "EMPLOYEES"
+ task.upsert_enabled = True
+ task.upsert_keys = ["employee_id"]
+ task.skip_column = "processed"
+ task.skip_value = "Y"
+ return task
+
+ def _seed(self, db, employee_id, name, processed):
+ from sqlalchemy import text
+
+ db.execute(
+ text(
+ 'INSERT INTO "EMPLOYEES" ("employee_id", "name", "processed") VALUES (:i, :n, :p)'
+ ),
+ {"i": employee_id, "n": name, "p": processed},
+ )
+ db.commit()
- results = process_rows_with_upsert(mock_db, mock_task_upsert_with_skip, 1, rows)
+ def _fetch_name(self, db, employee_id):
+ from sqlalchemy import text
+
+ return db.execute(
+ text('SELECT "name" FROM "EMPLOYEES" WHERE "employee_id" = :i'),
+ {"i": employee_id},
+ ).scalar()
+
+ def test_row_marked_processed_is_skipped(self, sqlite_dest, task_with_skip):
+ """An existing row whose skip column matches is never overwritten."""
+ self._seed(sqlite_dest, 1, "Already Processed", "Y")
+ self._seed(sqlite_dest, 2, "Stale", "N")
+
+ rows = [
+ {"employee_id": 1, "name": "Overwrite Attempt", "processed": "Y"},
+ {"employee_id": 2, "name": "Fresh Value", "processed": "N"},
+ ]
+ results = process_rows_with_upsert(sqlite_dest, task_with_skip, 1, rows, app_db=sqlite_dest)
- assert results["inserted"] == 1
- assert results["updated"] == 1
assert results["skipped"] == 1
- assert results["errors"] == 0
- mock_insert.assert_called_once()
- mock_update.assert_called_once()
+ assert results["updated"] == 1
+ assert self._fetch_name(sqlite_dest, 1) == "Already Processed"
+ assert self._fetch_name(sqlite_dest, 2) == "Fresh Value"
if __name__ == "__main__":
From be6eeeab22819eb64343c9b371b3dab29a428f11 Mon Sep 17 00:00:00 2001
From: badry-dev <181844990+badry-dev@users.noreply.github.com>
Date: Sat, 22 Aug 2026 13:52:20 +0000
Subject: [PATCH 3/7] fix(review): harden CI actions, mask generated Fernet
key, normalize APP_ENV
- Pin all GitHub Actions to full-length commit SHAs and set persist-credentials: false on every checkout (zizmor artipacked/unpinned-uses findings)
- Mask the per-run generated Fernet key via ::add-mask:: before exporting to GITHUB_ENV; drop the plaintext fernet_key.txt intermediate file
- Normalize APP_ENV (strip + lowercase) so production gating of /docs and /openapi.json can't be bypassed by 'Production' or padded values; add tests for normalization, missing-value default, and docs gating in dev/production
Co-authored-by: kiloconnect[bot] <240665456+kiloconnect[bot]@users.noreply.github.com>
---
.github/workflows/ci.yml | 51 +++++++++++-------
backend/app/core/config.py | 7 +++
backend/tests/unit/test_config_env.py | 77 +++++++++++++++++++++++++++
3 files changed, 117 insertions(+), 18 deletions(-)
create mode 100644 backend/tests/unit/test_config_env.py
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index ad419b4..1755a7a 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -15,9 +15,11 @@ jobs:
run:
working-directory: backend
steps:
- - uses: actions/checkout@v4
+ - uses: actions/checkout@11bd71901bbe5b1630ceea73d27b97cfbf009fca # v4.2.2
+ with:
+ persist-credentials: false
- - uses: actions/setup-python@v5
+ - uses: actions/setup-python@0b93645e9fea7318ecaed2b359559ac225c90a2b # v5.6.0
with:
python-version: "3.11"
cache: pip
@@ -45,15 +47,18 @@ jobs:
ENCRYPTION_KEY: ${{ secrets.ENCRYPTION_KEY }}
REDIS_URL: redis://localhost:6379/0
steps:
- - uses: actions/checkout@v4
+ - uses: actions/checkout@11bd71901bbe5b1630ceea73d27b97cfbf009fca # v4.2.2
+ with:
+ persist-credentials: false
- name: Generate per-run Fernet key
if: ${{ env.ENCRYPTION_KEY == '' }}
run: |
- python -c "import base64, os; print(base64.urlsafe_b64encode(os.urandom(32)).decode())" > fernet_key.txt
- echo "ENCRYPTION_KEY=$(cat fernet_key.txt)" >> "$GITHUB_ENV"
+ KEY=$(python -c "import base64, os; print(base64.urlsafe_b64encode(os.urandom(32)).decode())")
+ echo "::add-mask::$KEY"
+ echo "ENCRYPTION_KEY=$KEY" >> "$GITHUB_ENV"
- - uses: actions/setup-python@v5
+ - uses: actions/setup-python@0b93645e9fea7318ecaed2b359559ac225c90a2b # v5.6.0
with:
python-version: "3.11"
cache: pip
@@ -72,7 +77,7 @@ jobs:
- name: Upload coverage report
if: always()
- uses: actions/upload-artifact@v4
+ uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: backend-coverage
path: backend/coverage.xml
@@ -84,9 +89,11 @@ jobs:
run:
working-directory: backend
steps:
- - uses: actions/checkout@v4
+ - uses: actions/checkout@11bd71901bbe5b1630ceea73d27b97cfbf009fca # v4.2.2
+ with:
+ persist-credentials: false
- - uses: actions/setup-python@v5
+ - uses: actions/setup-python@0b93645e9fea7318ecaed2b359559ac225c90a2b # v5.6.0
with:
python-version: "3.11"
cache: pip
@@ -111,9 +118,11 @@ jobs:
run:
working-directory: frontend
steps:
- - uses: actions/checkout@v4
+ - uses: actions/checkout@11bd71901bbe5b1630ceea73d27b97cfbf009fca # v4.2.2
+ with:
+ persist-credentials: false
- - uses: actions/setup-node@v4
+ - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
with:
node-version: "20"
cache: npm
@@ -135,9 +144,11 @@ jobs:
run:
working-directory: frontend
steps:
- - uses: actions/checkout@v4
+ - uses: actions/checkout@11bd71901bbe5b1630ceea73d27b97cfbf009fca # v4.2.2
+ with:
+ persist-credentials: false
- - uses: actions/setup-node@v4
+ - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
with:
node-version: "20"
cache: npm
@@ -156,9 +167,11 @@ jobs:
run:
working-directory: frontend
steps:
- - uses: actions/checkout@v4
+ - uses: actions/checkout@11bd71901bbe5b1630ceea73d27b97cfbf009fca # v4.2.2
+ with:
+ persist-credentials: false
- - uses: actions/setup-node@v4
+ - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
with:
node-version: "20"
cache: npm
@@ -171,7 +184,7 @@ jobs:
run: npm run build
- name: Upload build artifact
- uses: actions/upload-artifact@v4
+ uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: frontend-dist
path: frontend/dist/
@@ -184,9 +197,11 @@ jobs:
run:
working-directory: frontend
steps:
- - uses: actions/checkout@v4
+ - uses: actions/checkout@11bd71901bbe5b1630ceea73d27b97cfbf009fca # v4.2.2
+ with:
+ persist-credentials: false
- - uses: actions/setup-node@v4
+ - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
with:
node-version: "20"
cache: npm
diff --git a/backend/app/core/config.py b/backend/app/core/config.py
index 5eaa21c..3d62a87 100644
--- a/backend/app/core/config.py
+++ b/backend/app/core/config.py
@@ -87,6 +87,13 @@ def celery_backend(self) -> str:
def normalize_level(cls, v: str) -> str:
return v.upper()
+ @field_validator("APP_ENV")
+ @classmethod
+ def normalize_env(cls, v: str) -> str:
+ """Normalize APP_ENV so production gating (docs, API_TOKEN fail-closed)
+ can't be bypassed by 'Production', ' prod ', etc."""
+ return v.strip().lower()
+
class Config:
env_file = ".env"
case_sensitive = True
diff --git a/backend/tests/unit/test_config_env.py b/backend/tests/unit/test_config_env.py
new file mode 100644
index 0000000..bf12e66
--- /dev/null
+++ b/backend/tests/unit/test_config_env.py
@@ -0,0 +1,77 @@
+"""Tests for environment normalization and docs gating (v1.4 review)."""
+
+import pytest
+
+from app.core.config import Settings
+
+
+class TestAppEnvNormalization:
+ @pytest.mark.parametrize(
+ "raw,expected",
+ [
+ ("dev", "dev"),
+ ("Production", "production"),
+ (" PRODUCTION ", "production"),
+ ("Dev", "dev"),
+ ],
+ )
+ def test_app_env_normalized(self, raw, expected):
+ settings = Settings(APP_ENV=raw, SECRET_KEY="x")
+ assert settings.APP_ENV == expected
+
+ def test_missing_app_env_defaults_to_dev(self):
+ # Missing APP_ENV falls back to the dev default: interactive docs
+ # stay enabled outside production by design.
+ import os
+
+ env = {k: v for k, v in os.environ.items() if k != "APP_ENV"}
+ import subprocess
+ import sys
+ from pathlib import Path
+
+ backend = Path(__file__).resolve().parents[2]
+ code = (
+ "from app.core.config import Settings;"
+ "print(Settings(SECRET_KEY='x').APP_ENV)"
+ )
+ out = subprocess.run(
+ [sys.executable, "-c", code],
+ env=env,
+ cwd=str(backend),
+ capture_output=True,
+ text=True,
+ )
+ assert out.stdout.strip() == "dev"
+
+
+class TestDocsGating:
+ def _make_app(self, app_env):
+ import importlib
+
+ from app.core.config import settings
+
+ original = settings.APP_ENV
+ settings.APP_ENV = app_env
+ try:
+ import app.main as main_module
+
+ importlib.reload(main_module)
+ return main_module.app
+ finally:
+ settings.APP_ENV = original
+
+ def test_docs_enabled_outside_production(self):
+ from fastapi.testclient import TestClient
+
+ app = self._make_app("dev")
+ client = TestClient(app)
+ assert client.get("/docs").status_code == 200
+ assert client.get("/openapi.json").status_code == 200
+
+ def test_docs_disabled_in_production_canonical_value(self):
+ from fastapi.testclient import TestClient
+
+ app = self._make_app("production")
+ client = TestClient(app)
+ assert client.get("/docs").status_code == 404
+ assert client.get("/openapi.json").status_code == 404
From e15d3dc42bf5a174f264afbc4f0c74c383b79bfa Mon Sep 17 00:00:00 2001
From: badry-dev <181844990+badry-dev@users.noreply.github.com>
Date: Sat, 22 Aug 2026 14:08:02 +0000
Subject: [PATCH 4/7] fix(review): scheduler dispatch isolation, auto-pause,
batch dedupe, alembic fixes
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
- Scheduler: isolate enqueue_run in its own try so only dispatch failures increment consecutive_failures; metadata/bookkeeping failures log + roll back without counting. Auto-pause at SCHEDULE_MAX_CONSECUTIVE_FAILURES (default 5): deactivates the schedule, removes the APScheduler job; resume endpoint resets the counter (review findings on PR #13)
- Batch upsert: deduplicate rows sharing an upsert-key tuple within a batch — first occurrence wins, duplicates counted as skipped with attribution (prevents silently-dropped WHEN clauses / duplicate inserts)
- skip_value checks use 'is not None' so configured empty-string skip values are honored
- Alembic: shorten revision id to 004_sched_fail_ctr (fits VARCHAR(32) alembic_version on Oracle); point env.py at APP_DATABASE_URL (app-state DB) instead of settings.sqlalchemy_url (destination Oracle)
- Tests: reset test starts with nonzero counter; date fields init None; bookkeeping-failure isolation; threshold pause + below-threshold cases; duplicate-key policy test
Co-authored-by: kiloconnect[bot] <240665456+kiloconnect[bot]@users.noreply.github.com>
---
backend/alembic/env.py | 7 +-
...tive_failures.py => 004_sched_fail_ctr.py} | 5 +-
backend/app/api/v1/routes/schedules.py | 1 +
backend/app/core/config.py | 3 +
backend/app/services/runner.py | 34 +++++++-
backend/app/services/scheduler.py | 79 +++++++++++++------
backend/tests/unit/test_config_env.py | 5 +-
backend/tests/unit/test_scheduler.py | 64 ++++++++++++++-
backend/tests/unit/test_upsert_logic.py | 33 ++++++++
9 files changed, 192 insertions(+), 39 deletions(-)
rename backend/alembic/versions/{004_schedule_consecutive_failures.py => 004_sched_fail_ctr.py} (87%)
diff --git a/backend/alembic/env.py b/backend/alembic/env.py
index c5a3936..cdbc9d5 100644
--- a/backend/alembic/env.py
+++ b/backend/alembic/env.py
@@ -43,7 +43,9 @@ def run_migrations_offline() -> None:
"""
from app.core.config import settings
- url = settings.sqlalchemy_url
+ # Migrations target the APP-STATE database (task/task_run/schedule tables),
+ # NOT the destination Oracle connection (settings.sqlalchemy_url).
+ url = settings.APP_DATABASE_URL
context.configure(
url=url,
target_metadata=target_metadata,
@@ -65,7 +67,8 @@ def run_migrations_online() -> None:
from app.core.config import settings
configuration = config.get_section(config.config_ini_section)
- configuration["sqlalchemy.url"] = settings.sqlalchemy_url
+ # App-state DB — see note in run_migrations_offline().
+ configuration["sqlalchemy.url"] = settings.APP_DATABASE_URL
connectable = engine_from_config(
configuration,
diff --git a/backend/alembic/versions/004_schedule_consecutive_failures.py b/backend/alembic/versions/004_sched_fail_ctr.py
similarity index 87%
rename from backend/alembic/versions/004_schedule_consecutive_failures.py
rename to backend/alembic/versions/004_sched_fail_ctr.py
index 1ded4d0..96fa586 100644
--- a/backend/alembic/versions/004_schedule_consecutive_failures.py
+++ b/backend/alembic/versions/004_sched_fail_ctr.py
@@ -1,6 +1,6 @@
"""Add consecutive_failures counter to task_schedule
-Revision ID: 004_schedule_consecutive_failures
+Revision ID: 004_sched_fail_ctr
Revises: 003_p0_oauth_ratelimit_cursor
Create Date: 2026-08-22 00:00:00.000000
@@ -12,7 +12,8 @@
import sqlalchemy as sa
-revision = '004_schedule_consecutive_failures'
+# Revision id must fit alembic_version.version_num VARCHAR(32).
+revision = '004_sched_fail_ctr'
down_revision = '003_p0_oauth_ratelimit_cursor'
branch_labels = None
depends_on = None
diff --git a/backend/app/api/v1/routes/schedules.py b/backend/app/api/v1/routes/schedules.py
index e9c05f6..ca70318 100644
--- a/backend/app/api/v1/routes/schedules.py
+++ b/backend/app/api/v1/routes/schedules.py
@@ -265,6 +265,7 @@ def resume_schedule(schedule_id: int, db: Session = Depends(get_db)):
# Reset failure tracking and reactivate
schedule.is_active = True
+ schedule.consecutive_failures = 0
schedule.updated_at = datetime.now(UTC)
db.commit()
diff --git a/backend/app/core/config.py b/backend/app/core/config.py
index 3d62a87..ab44840 100644
--- a/backend/app/core/config.py
+++ b/backend/app/core/config.py
@@ -34,6 +34,9 @@ class Settings(BaseSettings):
ENCRYPTION_KEY: str | None = None
CONNECTIONS_FILE_PATH: str = "connections.enc"
+ # Auto-pause a schedule after this many consecutive dispatch failures.
+ SCHEDULE_MAX_CONSECUTIVE_FAILURES: int = 5
+
@property
def destination_sqlalchemy_url(self) -> str:
# Using oracledb with thin mode
diff --git a/backend/app/services/runner.py b/backend/app/services/runner.py
index 2a61dcf..1420ddc 100644
--- a/backend/app/services/runner.py
+++ b/backend/app/services/runner.py
@@ -721,6 +721,32 @@ def _process_upsert_batch(
results = {"inserted": 0, "updated": 0, "skipped": 0, "errors": 0, "error_details": []}
+ if not batch:
+ return results
+
+ # Deduplicate rows sharing the same upsert-key tuple WITHIN this batch.
+ # Two rows with identical keys would both enter to_update (later WHEN
+ # clauses silently dropped by SQL) or both to_insert (constraint failure
+ # or duplicate rows). First occurrence wins; duplicates are counted as
+ # skipped with an attribution entry.
+ seen_keys = set()
+ deduped_batch = []
+ for idx, row in enumerate(batch):
+ key_tuple = tuple(row.get(key) for key in upsert_keys)
+ if key_tuple in seen_keys:
+ results["skipped"] += 1
+ results["error_details"].append(
+ {
+ "row_index": batch_offset + idx,
+ "record_key": _get_record_key(row, upsert_keys),
+ "error": "duplicate upsert key within batch; first occurrence wins",
+ }
+ )
+ continue
+ seen_keys.add(key_tuple)
+ deduped_batch.append(row)
+ batch = deduped_batch
+
if not batch:
return results
@@ -751,7 +777,7 @@ def _process_upsert_batch(
# configured so matching rows can be routed to `to_skip` without a
# second per-row query.
select_columns = list(upsert_keys)
- if task.skip_column and task.skip_value:
+ if task.skip_column and task.skip_value is not None:
skip_col = _clean_identifier(task.skip_column)
if skip_col not in select_columns:
select_columns.append(skip_col)
@@ -784,7 +810,7 @@ def _process_upsert_batch(
if existing is None:
# New record
to_insert.append((batch_offset + idx, row))
- elif task.skip_column and task.skip_value and _should_skip(task, existing):
+ elif task.skip_column and task.skip_value is not None and _should_skip(task, existing):
# Record exists and matches the configured skip condition —
# third parties may have marked this row processed; never
# overwrite it.
@@ -807,7 +833,7 @@ def _process_upsert_batch(
db.commit()
logger.debug(f"Bulk inserted {insert_count} rows")
- results["skipped"] = len(to_skip)
+ results["skipped"] += len(to_skip)
except Exception as e:
db.rollback()
@@ -1030,7 +1056,7 @@ def _find_existing_record(
def _should_skip(task: Task, existing_record: dict) -> bool:
"""Check if record should be skipped based on skip_column/skip_value."""
- if not task.skip_column or not task.skip_value:
+ if not task.skip_column or task.skip_value is None:
return False
current_value = existing_record.get(task.skip_column.upper()) # Oracle returns uppercase
diff --git a/backend/app/services/scheduler.py b/backend/app/services/scheduler.py
index 2125ccb..0a631f7 100644
--- a/backend/app/services/scheduler.py
+++ b/backend/app/services/scheduler.py
@@ -112,16 +112,26 @@ def remove_schedule(self, task_id: int):
def _execute_scheduled_task(self, task_id: int, task_name: str):
"""Execute a scheduled task by enqueueing to Celery"""
+ logger.info(f"Triggering scheduled import for task {task_id} '{task_name}'")
+
+ # Enqueue task to Celery. enqueue_run() itself delegates to
+ # run_import_task.delay(); calling .delay on it again would raise
+ # AttributeError on every cron fire (the function is not a Celery
+ # task itself).
+ #
+ # Dispatch is isolated in its own try block so that only enqueue
+ # failures count toward consecutive_failures — later bookkeeping
+ # errors (schedule query, cron calc, metadata commit) must not
+ # record a dispatch failure for an import Celery already accepted.
try:
- logger.info(f"Triggering scheduled import for task {task_id} '{task_name}'")
-
- # Enqueue task to Celery. enqueue_run() itself delegates to
- # run_import_task.delay(); calling .delay on it again would raise
- # AttributeError on every cron fire (the function is not a Celery
- # task itself).
result = enqueue_run(task_id)
+ except Exception as dispatch_exc:
+ self._handle_dispatch_failure(task_id, dispatch_exc)
+ return
- # Update last_run_date and next_run_date
+ # Update last_run_date and next_run_date. Bookkeeping failures are
+ # logged and rolled back WITHOUT touching the failure counter.
+ try:
task_schedule = (
self.db.query(TaskSchedule).filter(TaskSchedule.task_id == task_id).first()
)
@@ -141,31 +151,50 @@ def _execute_scheduled_task(self, task_id: int, task_name: str):
logger.info(
f"Enqueued task {task_id} to Celery (job_id: {result.id}). Next run: {next_run}"
)
-
except Exception as e:
- logger.error(f"Failed to execute scheduled task {task_id}: {str(e)}")
+ logger.error(f"Failed to update schedule metadata for task {task_id}: {str(e)}")
self.db.rollback()
- # Persist the failure counter in a separate transaction: the
- # rollback above discards anything staged before the error, so the
- # increment must happen after it.
- try:
- task_schedule = (
- self.db.query(TaskSchedule).filter(TaskSchedule.task_id == task_id).first()
- )
- if task_schedule:
- current = task_schedule.consecutive_failures or 0
- task_schedule.consecutive_failures = current + 1
- self.db.commit()
+ def _handle_dispatch_failure(self, task_id: int, exc: Exception):
+ """Record a dispatch failure and auto-pause at the configured threshold.
+
+ The counter is persisted in a separate transaction AFTER the rollback:
+ the rollback discards anything staged before the error.
+ """
+ from app.core.config import settings
+
+ logger.error(f"Failed to execute scheduled task {task_id}: {str(exc)}")
+ self.db.rollback()
+
+ threshold = getattr(settings, "SCHEDULE_MAX_CONSECUTIVE_FAILURES", 5)
+
+ try:
+ task_schedule = (
+ self.db.query(TaskSchedule).filter(TaskSchedule.task_id == task_id).first()
+ )
+ if task_schedule:
+ current = task_schedule.consecutive_failures or 0
+ task_schedule.consecutive_failures = current + 1
+
+ if current + 1 >= threshold and task_schedule.is_active:
+ task_schedule.is_active = False
+ self.remove_schedule(task_id)
logger.error(
- f"Scheduled task {task_id} dispatch failure "
- f"(consecutive_failures={current + 1})"
+ f"Auto-paused schedule for task {task_id}: "
+ f"{current + 1} consecutive dispatch failures "
+ f"(threshold={threshold}). Use the resume endpoint to reactivate."
)
- except Exception as counter_exc:
+
+ self.db.commit()
logger.error(
- f"Failed to persist consecutive_failures for task {task_id}: {counter_exc}"
+ f"Scheduled task {task_id} dispatch failure "
+ f"(consecutive_failures={current + 1})"
)
- self.db.rollback()
+ except Exception as counter_exc:
+ logger.error(
+ f"Failed to persist consecutive_failures for task {task_id}: {counter_exc}"
+ )
+ self.db.rollback()
def reload_schedules(self):
"""Reload all schedules from database (useful after schedule updates)"""
diff --git a/backend/tests/unit/test_config_env.py b/backend/tests/unit/test_config_env.py
index bf12e66..49211c1 100644
--- a/backend/tests/unit/test_config_env.py
+++ b/backend/tests/unit/test_config_env.py
@@ -30,10 +30,7 @@ def test_missing_app_env_defaults_to_dev(self):
from pathlib import Path
backend = Path(__file__).resolve().parents[2]
- code = (
- "from app.core.config import Settings;"
- "print(Settings(SECRET_KEY='x').APP_ENV)"
- )
+ code = "from app.core.config import Settings;print(Settings(SECRET_KEY='x').APP_ENV)"
out = subprocess.run(
[sys.executable, "-c", code],
env=env,
diff --git a/backend/tests/unit/test_scheduler.py b/backend/tests/unit/test_scheduler.py
index 055ba93..fced82d 100644
--- a/backend/tests/unit/test_scheduler.py
+++ b/backend/tests/unit/test_scheduler.py
@@ -25,11 +25,14 @@ def scheduler():
yield sched, db
-def _make_schedule(consecutive_failures=0):
+def _make_schedule(consecutive_failures=0, is_active=True):
schedule = MagicMock(spec=TaskSchedule)
schedule.task_id = 7
schedule.cron_expression = "0 2 * * *"
schedule.consecutive_failures = consecutive_failures
+ schedule.is_active = is_active
+ schedule.last_run_date = None
+ schedule.next_run_date = None
return schedule
@@ -39,7 +42,7 @@ def test_dispatch_invokes_enqueue_run(self, scheduler):
run_import_task.delay — not enqueue_run.delay (AttributeError)."""
sched, db = scheduler
- schedule = _make_schedule()
+ schedule = _make_schedule(consecutive_failures=2)
db.query.return_value.filter.return_value.first.return_value = schedule
with patch("app.services.scheduler.enqueue_run") as mock_enqueue:
@@ -47,6 +50,7 @@ def test_dispatch_invokes_enqueue_run(self, scheduler):
sched._execute_scheduled_task(7, "Nightly import")
mock_enqueue.assert_called_once_with(7)
+ # Successful dispatch resets the failure counter
assert schedule.consecutive_failures == 0
def test_dispatch_updates_schedule_dates_on_success(self, scheduler):
@@ -104,6 +108,62 @@ def test_counter_commit_failure_is_swallowed(self, scheduler):
# Must not raise
sched._execute_scheduled_task(7, "Nightly import")
+ def test_bookkeeping_failure_does_not_count_as_dispatch_failure(self, scheduler):
+ """A failure AFTER a successful enqueue (e.g. metadata commit) must not
+ increment consecutive_failures — Celery already accepted the job."""
+ sched, db = scheduler
+ schedule = _make_schedule(consecutive_failures=4)
+ db.query.return_value.filter.return_value.first.return_value = schedule
+ db.commit.side_effect = RuntimeError("sqlite locked")
+
+ with (
+ patch("app.services.scheduler.enqueue_run") as mock_enqueue,
+ patch.object(sched, "_handle_dispatch_failure") as mock_handle,
+ ):
+ mock_enqueue.return_value = MagicMock(id="celery-id-3")
+ sched._execute_scheduled_task(7, "Nightly import")
+
+ # Dispatch succeeded; the metadata failure is handled separately and
+ # must NOT be recorded as a dispatch failure.
+ mock_enqueue.assert_called_once()
+ mock_handle.assert_not_called()
+ db.rollback.assert_called_once()
+
+ def test_auto_pause_at_threshold_deactivates_schedule(self, scheduler):
+ """Reaching the consecutive-failure threshold deactivates the schedule
+ and removes its APScheduler job."""
+ sched, db = scheduler
+ schedule = _make_schedule(consecutive_failures=4, is_active=True)
+ db.query.return_value.filter.return_value.first.return_value = schedule
+
+ with (
+ patch("app.services.scheduler.enqueue_run", side_effect=RuntimeError("down")),
+ patch.object(sched, "remove_schedule") as mock_remove,
+ patch("app.core.config.settings") as mock_settings,
+ ):
+ mock_settings.SCHEDULE_MAX_CONSECUTIVE_FAILURES = 5
+ sched._execute_scheduled_task(7, "Nightly import")
+
+ assert schedule.is_active is False
+ assert schedule.consecutive_failures == 5
+ mock_remove.assert_called_once_with(7)
+
+ def test_below_threshold_does_not_pause(self, scheduler):
+ sched, db = scheduler
+ schedule = _make_schedule(consecutive_failures=1, is_active=True)
+ db.query.return_value.filter.return_value.first.return_value = schedule
+
+ with (
+ patch("app.services.scheduler.enqueue_run", side_effect=RuntimeError("down")),
+ patch.object(sched, "remove_schedule") as mock_remove,
+ patch("app.core.config.settings") as mock_settings,
+ ):
+ mock_settings.SCHEDULE_MAX_CONSECUTIVE_FAILURES = 5
+ sched._execute_scheduled_task(7, "Nightly import")
+
+ assert schedule.is_active is True
+ mock_remove.assert_not_called()
+
if __name__ == "__main__":
pytest.main([__file__, "-v"])
diff --git a/backend/tests/unit/test_upsert_logic.py b/backend/tests/unit/test_upsert_logic.py
index 3a600db..eb2951f 100644
--- a/backend/tests/unit/test_upsert_logic.py
+++ b/backend/tests/unit/test_upsert_logic.py
@@ -475,6 +475,39 @@ def test_column_present_only_in_later_row_is_updated(self, sqlite_dest, task):
assert email_val == "row2@example.com"
+class TestDuplicateUpsertKeysInBatch:
+ """Duplicate upsert-key tuples within one batch: first occurrence wins,
+ duplicates are counted as skipped (v1.4 review finding)."""
+
+ @pytest.fixture
+ def task(self):
+ task = MagicMock(spec=Task)
+ task.id = 1
+ task.dest_table = "EMPLOYEES"
+ task.upsert_enabled = True
+ task.upsert_keys = ["employee_id"]
+ task.skip_column = None
+ task.skip_value = None
+ return task
+
+ def test_duplicate_keys_first_row_wins(self, task):
+ db = MagicMock()
+ db.execute.return_value.fetchall.return_value = [] # nothing exists
+
+ rows = [
+ {"employee_id": 1, "name": "First"},
+ {"employee_id": 1, "name": "Second"}, # duplicate key
+ ]
+ with patch("app.services.runner.insert_batch", return_value=1):
+ results = process_rows_with_upsert(db, task, 1, rows)
+
+ assert results["inserted"] == 1
+ assert results["skipped"] == 1
+ assert any(
+ "duplicate upsert key" in str(d.get("error", "")) for d in results["error_details"]
+ )
+
+
class TestBatchSkipCondition:
"""Regression tests for the unimplemented skip condition in the batch
upsert path (v1.4 C3)."""
From 4c6f3b23abd01c596028821d12a62729b4cd6134 Mon Sep 17 00:00:00 2001
From: badry-dev
Date: Tue, 1 Sep 2026 19:52:46 +0000
Subject: [PATCH 5/7] ci: correct pinned commit SHAs for actions/checkout and
actions/setup-python
The phase-0 pin for actions/checkout referenced
11bd71901bbe5b1630ceea73d27b97cfbf009fca, which does not exist in the
action repository (v4.2.2 is 11bd71901bbe5b1630ceea73d27597364c9af683).
Every job failed in "Set up job" with "Unable to resolve action ...
unable to find version" before any project code ran.
actions/setup-python was pinned to 0b93645e (v5.3.0) while the comment
claimed v5.6.0; pin the real v5.6.0 commit (a26af69b).
Claude-Session: https://claude.ai/code/session_018bq3je2VeLdrF71FThQVvU
---
.github/workflows/ci.yml | 20 ++++++++++----------
1 file changed, 10 insertions(+), 10 deletions(-)
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 1755a7a..f87349f 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -15,11 +15,11 @@ jobs:
run:
working-directory: backend
steps:
- - uses: actions/checkout@11bd71901bbe5b1630ceea73d27b97cfbf009fca # v4.2.2
+ - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
persist-credentials: false
- - uses: actions/setup-python@0b93645e9fea7318ecaed2b359559ac225c90a2b # v5.6.0
+ - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
with:
python-version: "3.11"
cache: pip
@@ -47,7 +47,7 @@ jobs:
ENCRYPTION_KEY: ${{ secrets.ENCRYPTION_KEY }}
REDIS_URL: redis://localhost:6379/0
steps:
- - uses: actions/checkout@11bd71901bbe5b1630ceea73d27b97cfbf009fca # v4.2.2
+ - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
persist-credentials: false
@@ -58,7 +58,7 @@ jobs:
echo "::add-mask::$KEY"
echo "ENCRYPTION_KEY=$KEY" >> "$GITHUB_ENV"
- - uses: actions/setup-python@0b93645e9fea7318ecaed2b359559ac225c90a2b # v5.6.0
+ - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
with:
python-version: "3.11"
cache: pip
@@ -89,11 +89,11 @@ jobs:
run:
working-directory: backend
steps:
- - uses: actions/checkout@11bd71901bbe5b1630ceea73d27b97cfbf009fca # v4.2.2
+ - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
persist-credentials: false
- - uses: actions/setup-python@0b93645e9fea7318ecaed2b359559ac225c90a2b # v5.6.0
+ - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
with:
python-version: "3.11"
cache: pip
@@ -118,7 +118,7 @@ jobs:
run:
working-directory: frontend
steps:
- - uses: actions/checkout@11bd71901bbe5b1630ceea73d27b97cfbf009fca # v4.2.2
+ - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
persist-credentials: false
@@ -144,7 +144,7 @@ jobs:
run:
working-directory: frontend
steps:
- - uses: actions/checkout@11bd71901bbe5b1630ceea73d27b97cfbf009fca # v4.2.2
+ - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
persist-credentials: false
@@ -167,7 +167,7 @@ jobs:
run:
working-directory: frontend
steps:
- - uses: actions/checkout@11bd71901bbe5b1630ceea73d27b97cfbf009fca # v4.2.2
+ - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
persist-credentials: false
@@ -197,7 +197,7 @@ jobs:
run:
working-directory: frontend
steps:
- - uses: actions/checkout@11bd71901bbe5b1630ceea73d27b97cfbf009fca # v4.2.2
+ - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
persist-credentials: false
From 3c730ac1a124af6eb37d73d3a34e1d8c76710e9b Mon Sep 17 00:00:00 2001
From: badry-dev
Date: Tue, 1 Sep 2026 19:52:47 +0000
Subject: [PATCH 6/7] test: align two stale integration tests with current
contracts; ruff-format test_config_env
- test_trigger_task_run asserted a run_id key, but POST /tasks/{id}/run
has returned {status, task_id, celery_task_id} since the worker took
over TaskRun creation. Assert the celery_task_id the mocked enqueue
returns instead.
- test_logging_context_propagation ran run_import without any column
mappings; the runner now refuses to run a task with no active
mappings ("direct API-field inserts are disabled"). Use the existing
column_mappings fixture like the neighbouring pipeline tests.
- tests/unit/test_config_env.py: apply ruff format so the
"ruff format --check" job passes on this branch.
Claude-Session: https://claude.ai/code/session_018bq3je2VeLdrF71FThQVvU
---
backend/tests/integration/test_api_endpoints.py | 2 +-
backend/tests/integration/test_full_pipeline.py | 2 +-
backend/tests/unit/test_config_env.py | 5 +----
3 files changed, 3 insertions(+), 6 deletions(-)
diff --git a/backend/tests/integration/test_api_endpoints.py b/backend/tests/integration/test_api_endpoints.py
index 3dfa9c8..91d9cf4 100644
--- a/backend/tests/integration/test_api_endpoints.py
+++ b/backend/tests/integration/test_api_endpoints.py
@@ -240,7 +240,7 @@ def test_trigger_task_run(client: TestClient, sample_task, test_db):
data = response.json()
assert data["status"] == "enqueued"
assert data["task_id"] == sample_task.id
- assert data["run_id"] is not None
+ assert data["celery_task_id"] == f"task-{sample_task.id}"
def test_trigger_nonexistent_task_run(client: TestClient):
diff --git a/backend/tests/integration/test_full_pipeline.py b/backend/tests/integration/test_full_pipeline.py
index 7a28190..f5dceb3 100644
--- a/backend/tests/integration/test_full_pipeline.py
+++ b/backend/tests/integration/test_full_pipeline.py
@@ -283,7 +283,7 @@ async def test_import_with_validation_errors(
@pytest.mark.asyncio
-async def test_logging_context_propagation(test_db, sample_task):
+async def test_logging_context_propagation(test_db, sample_task, column_mappings):
"""Test that task_id and run_id propagate through async calls"""
from app.core.logging import clear_task_context, set_task_context
diff --git a/backend/tests/unit/test_config_env.py b/backend/tests/unit/test_config_env.py
index bf12e66..49211c1 100644
--- a/backend/tests/unit/test_config_env.py
+++ b/backend/tests/unit/test_config_env.py
@@ -30,10 +30,7 @@ def test_missing_app_env_defaults_to_dev(self):
from pathlib import Path
backend = Path(__file__).resolve().parents[2]
- code = (
- "from app.core.config import Settings;"
- "print(Settings(SECRET_KEY='x').APP_ENV)"
- )
+ code = "from app.core.config import Settings;print(Settings(SECRET_KEY='x').APP_ENV)"
out = subprocess.run(
[sys.executable, "-c", code],
env=env,
From 1c8e23272fc6cf509e827fac0301c3c6519a13a9 Mon Sep 17 00:00:00 2001
From: badry-dev
Date: Tue, 1 Sep 2026 19:58:28 +0000
Subject: [PATCH 7/7] build(deps): clear both dependency audits
(fastapi/starlette/cryptography, Vite/Vitest toolchain)
Backend (pip-audit reported 9 findings on main):
- cryptography 46.0.7 -> 50.0.1 (PYSEC-2026-3552/3553/3554, GHSA-537c-gmf6-5ccf)
- fastapi 0.115.6 -> 0.141.1 and starlette pinned at 1.6.0
(PYSEC-2026-161/248/249/2280/2281). fastapi 0.141 accepts
starlette>=0.46, so the "incompatible with fastapi 0.115.x" blocker
noted in the audit job is gone; drop the three --ignore-vuln flags.
pip-audit -r requirements.txt is clean with no ignores.
- Backend suite unchanged by the bump: 410 passed, the 6 remaining
upsert-logic failures are the ones phase 1 fixes.
Frontend (npm audit --audit-level=high reported 17 findings, 9 high, 2 critical):
- npm audit fix for the semver-compatible set (axios, undici, form-data,
js-yaml, nanoid, postcss, browserslist, brace-expansion, @babel/core).
- vite 5 -> 8.2.2, vitest and @vitest/ui 1 -> 4.1.11,
@vitejs/plugin-react 4 -> 6.1.1. The remaining high/critical findings
(vite server.fs.deny bypass, vitest UI arbitrary file read) are fixed
only in these majors. Dev/test/build toolchain only; typecheck, lint,
the 79 tests and the production build pass unchanged.
- react-router-dom stays on 6.x: its findings are moderate (below the
gate) and the fix is a runtime major.
Claude-Session: https://claude.ai/code/session_018bq3je2VeLdrF71FThQVvU
---
.github/workflows/ci.yml | 8 +-
backend/requirements.txt | 5 +-
frontend/package-lock.json | 2944 +++++++++++-------------------------
frontend/package.json | 8 +-
4 files changed, 865 insertions(+), 2100 deletions(-)
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index f87349f..49fbb98 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -102,13 +102,7 @@ jobs:
run: pip install pip-audit
- name: Audit dependencies
- run: |
- pip-audit -r requirements.txt \
- --ignore-vuln GHSA-jfh8-c2jp-hdp8 \
- --ignore-vuln CVE-2025-54121 \
- --ignore-vuln CVE-2025-62727
- # CVE-2025-54121 and CVE-2025-62727 (starlette) require starlette>=0.47/0.49
- # which is incompatible with fastapi 0.115.x — track in backlog.
+ run: pip-audit -r requirements.txt
# ── Frontend ───────────────────────────────────────────────────────────────
frontend-lint:
diff --git a/backend/requirements.txt b/backend/requirements.txt
index 88fe0e9..8872b0e 100644
--- a/backend/requirements.txt
+++ b/backend/requirements.txt
@@ -1,4 +1,5 @@
-fastapi==0.115.6
+fastapi==0.141.1
+starlette==1.6.0
uvicorn[standard]==0.32.1
pydantic==2.10.6
pydantic-settings==2.7.1
@@ -14,4 +15,4 @@ python-dotenv==1.2.2
loguru==0.7.2
python-dateutil==2.9.0.post0
croniter==2.0.7
-cryptography==46.0.7
+cryptography==50.0.1
diff --git a/frontend/package-lock.json b/frontend/package-lock.json
index 3f0148c..855ae90 100644
--- a/frontend/package-lock.json
+++ b/frontend/package-lock.json
@@ -29,15 +29,15 @@
"@types/react-dom": "^18.2.17",
"@typescript-eslint/eslint-plugin": "^8.59.1",
"@typescript-eslint/parser": "^8.59.1",
- "@vitejs/plugin-react": "^4.2.1",
- "@vitest/ui": "^1.1.0",
- "eslint": "^8.56.0",
+ "@vitejs/plugin-react": "^6.1.1",
+ "@vitest/ui": "^4.1.11",
+ "eslint": "^8.57.0",
"eslint-plugin-react-hooks": "^4.6.0",
"eslint-plugin-react-refresh": "^0.4.5",
"jsdom": "^29.0.2",
"typescript": "^5.3.3",
- "vite": "^5.0.8",
- "vitest": "^1.1.0"
+ "vite": "^8.2.2",
+ "vitest": "^4.1.11"
}
},
"node_modules/@adobe/css-tools": {
@@ -192,13 +192,13 @@
"license": "MIT"
},
"node_modules/@babel/code-frame": {
- "version": "7.28.6",
- "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.28.6.tgz",
- "integrity": "sha512-JYgintcMjRiCvS8mMECzaEn+m3PfoQiyqukOMCCVQtoJGYJw8j/8LBJEiqkHLkfwCcs74E3pbAUFNg7d9VNJ+Q==",
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.7.tgz",
+ "integrity": "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@babel/helper-validator-identifier": "^7.28.5",
+ "@babel/helper-validator-identifier": "^7.29.7",
"js-tokens": "^4.0.0",
"picocolors": "^1.1.1"
},
@@ -206,243 +206,14 @@
"node": ">=6.9.0"
}
},
- "node_modules/@babel/compat-data": {
- "version": "7.28.6",
- "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.6.tgz",
- "integrity": "sha512-2lfu57JtzctfIrcGMz992hyLlByuzgIk58+hhGCxjKZ3rWI82NnVLjXcaTqkI2NvlcvOskZaiZ5kjUALo3Lpxg==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/@babel/core": {
- "version": "7.28.6",
- "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.6.tgz",
- "integrity": "sha512-H3mcG6ZDLTlYfaSNi0iOKkigqMFvkTKlGUYlD8GW7nNOYRrevuA46iTypPyv+06V3fEmvvazfntkBU34L0azAw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/code-frame": "^7.28.6",
- "@babel/generator": "^7.28.6",
- "@babel/helper-compilation-targets": "^7.28.6",
- "@babel/helper-module-transforms": "^7.28.6",
- "@babel/helpers": "^7.28.6",
- "@babel/parser": "^7.28.6",
- "@babel/template": "^7.28.6",
- "@babel/traverse": "^7.28.6",
- "@babel/types": "^7.28.6",
- "@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/core/node_modules/semver": {
- "version": "6.3.1",
- "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
- "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
- "dev": true,
- "license": "ISC",
- "bin": {
- "semver": "bin/semver.js"
- }
- },
- "node_modules/@babel/generator": {
- "version": "7.28.6",
- "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.6.tgz",
- "integrity": "sha512-lOoVRwADj8hjf7al89tvQ2a1lf53Z+7tiXMgpZJL3maQPDxh0DgLMN62B2MKUOFcoodBHLMbDM6WAbKgNy5Suw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/parser": "^7.28.6",
- "@babel/types": "^7.28.6",
- "@jridgewell/gen-mapping": "^0.3.12",
- "@jridgewell/trace-mapping": "^0.3.28",
- "jsesc": "^3.0.2"
- },
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/@babel/helper-compilation-targets": {
- "version": "7.28.6",
- "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz",
- "integrity": "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/compat-data": "^7.28.6",
- "@babel/helper-validator-option": "^7.27.1",
- "browserslist": "^4.24.0",
- "lru-cache": "^5.1.1",
- "semver": "^6.3.1"
- },
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/@babel/helper-compilation-targets/node_modules/semver": {
- "version": "6.3.1",
- "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
- "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
- "dev": true,
- "license": "ISC",
- "bin": {
- "semver": "bin/semver.js"
- }
- },
- "node_modules/@babel/helper-globals": {
- "version": "7.28.0",
- "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz",
- "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/@babel/helper-module-imports": {
- "version": "7.28.6",
- "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz",
- "integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/traverse": "^7.28.6",
- "@babel/types": "^7.28.6"
- },
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/@babel/helper-module-transforms": {
- "version": "7.28.6",
- "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz",
- "integrity": "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-module-imports": "^7.28.6",
- "@babel/helper-validator-identifier": "^7.28.5",
- "@babel/traverse": "^7.28.6"
- },
- "engines": {
- "node": ">=6.9.0"
- },
- "peerDependencies": {
- "@babel/core": "^7.0.0"
- }
- },
- "node_modules/@babel/helper-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,
- "license": "MIT",
- "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"
- }
- },
"node_modules/@babel/helper-validator-identifier": {
- "version": "7.28.5",
- "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz",
- "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/@babel/helper-validator-option": {
- "version": "7.27.1",
- "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz",
- "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/@babel/helpers": {
- "version": "7.28.6",
- "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.6.tgz",
- "integrity": "sha512-xOBvwq86HHdB7WUDTfKfT/Vuxh7gElQ+Sfti2Cy6yIWNW05P8iUslOVcZ4/sKbE+/jQaukQAdz/gf3724kYdqw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/template": "^7.28.6",
- "@babel/types": "^7.28.6"
- },
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/@babel/parser": {
- "version": "7.28.6",
- "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.6.tgz",
- "integrity": "sha512-TeR9zWR18BvbfPmGbLampPMW+uW1NZnJlRuuHso8i87QZNq2JRF9i6RgxRqtEq+wQGsS19NNTWr2duhnE49mfQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/types": "^7.28.6"
- },
- "bin": {
- "parser": "bin/babel-parser.js"
- },
- "engines": {
- "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,
- "license": "MIT",
- "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==",
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz",
+ "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==",
"dev": true,
"license": "MIT",
- "dependencies": {
- "@babel/helper-plugin-utils": "^7.27.1"
- },
"engines": {
"node": ">=6.9.0"
- },
- "peerDependencies": {
- "@babel/core": "^7.0.0-0"
}
},
"node_modules/@babel/runtime": {
@@ -454,54 +225,6 @@
"node": ">=6.9.0"
}
},
- "node_modules/@babel/template": {
- "version": "7.28.6",
- "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz",
- "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/code-frame": "^7.28.6",
- "@babel/parser": "^7.28.6",
- "@babel/types": "^7.28.6"
- },
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/@babel/traverse": {
- "version": "7.28.6",
- "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.6.tgz",
- "integrity": "sha512-fgWX62k02qtjqdSNTAGxmKYY/7FSL9WAS1o2Hu5+I5m9T0yxZzr4cnrfXQ/MX0rIifthCSs6FKTlzYbJcPtMNg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/code-frame": "^7.28.6",
- "@babel/generator": "^7.28.6",
- "@babel/helper-globals": "^7.28.0",
- "@babel/parser": "^7.28.6",
- "@babel/template": "^7.28.6",
- "@babel/types": "^7.28.6",
- "debug": "^4.3.1"
- },
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/@babel/types": {
- "version": "7.28.6",
- "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.6.tgz",
- "integrity": "sha512-0ZrskXVEHSWIqZM/sQZ4EV3jZJXRkio/WCxaqKZP1g//CEWEPSfeZFcms4XeKBCHU0ZKnIkdJeU/kF+eRp5lBg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-string-parser": "^7.27.1",
- "@babel/helper-validator-identifier": "^7.28.5"
- },
- "engines": {
- "node": ">=6.9.0"
- }
- },
"node_modules/@bramus/specificity": {
"version": "2.4.2",
"resolved": "https://registry.npmjs.org/@bramus/specificity/-/specificity-2.4.2.tgz",
@@ -568,496 +291,105 @@
{
"type": "github",
"url": "https://github.com/sponsors/csstools"
- },
- {
- "type": "opencollective",
- "url": "https://opencollective.com/csstools"
- }
- ],
- "license": "MIT",
- "dependencies": {
- "@csstools/color-helpers": "^6.0.2",
- "@csstools/css-calc": "^3.2.0"
- },
- "engines": {
- "node": ">=20.19.0"
- },
- "peerDependencies": {
- "@csstools/css-parser-algorithms": "^4.0.0",
- "@csstools/css-tokenizer": "^4.0.0"
- }
- },
- "node_modules/@csstools/css-parser-algorithms": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-4.0.0.tgz",
- "integrity": "sha512-+B87qS7fIG3L5h3qwJ/IFbjoVoOe/bpOdh9hAjXbvx0o8ImEmUsGXN0inFOnk2ChCFgqkkGFQ+TpM5rbhkKe4w==",
- "dev": true,
- "funding": [
- {
- "type": "github",
- "url": "https://github.com/sponsors/csstools"
- },
- {
- "type": "opencollective",
- "url": "https://opencollective.com/csstools"
- }
- ],
- "license": "MIT",
- "engines": {
- "node": ">=20.19.0"
- },
- "peerDependencies": {
- "@csstools/css-tokenizer": "^4.0.0"
- }
- },
- "node_modules/@csstools/css-syntax-patches-for-csstree": {
- "version": "1.1.3",
- "resolved": "https://registry.npmjs.org/@csstools/css-syntax-patches-for-csstree/-/css-syntax-patches-for-csstree-1.1.3.tgz",
- "integrity": "sha512-SH60bMfrRCJF3morcdk57WklujF4Jr/EsQUzqkarfHXEFcAR1gg7fS/chAE922Sehgzc1/+Tz5H3Ypa1HiEKrg==",
- "dev": true,
- "funding": [
- {
- "type": "github",
- "url": "https://github.com/sponsors/csstools"
- },
- {
- "type": "opencollective",
- "url": "https://opencollective.com/csstools"
- }
- ],
- "license": "MIT-0",
- "peerDependencies": {
- "css-tree": "^3.2.1"
- },
- "peerDependenciesMeta": {
- "css-tree": {
- "optional": true
- }
- }
- },
- "node_modules/@csstools/css-tokenizer": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-4.0.0.tgz",
- "integrity": "sha512-QxULHAm7cNu72w97JUNCBFODFaXpbDg+dP8b/oWFAZ2MTRppA3U00Y2L1HqaS4J6yBqxwa/Y3nMBaxVKbB/NsA==",
- "dev": true,
- "funding": [
- {
- "type": "github",
- "url": "https://github.com/sponsors/csstools"
- },
- {
- "type": "opencollective",
- "url": "https://opencollective.com/csstools"
- }
- ],
- "license": "MIT",
- "engines": {
- "node": ">=20.19.0"
- }
- },
- "node_modules/@emotion/hash": {
- "version": "0.8.0",
- "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.8.0.tgz",
- "integrity": "sha512-kBJtf7PH6aWwZ6fka3zQ0p6SBYzx4fl1LoZXE2RrnYST9Xljm7WfKJrU4g/Xr3Beg72MLrp1AWNUmuYJTL7Cow==",
- "license": "MIT"
- },
- "node_modules/@emotion/unitless": {
- "version": "0.7.5",
- "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.7.5.tgz",
- "integrity": "sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg==",
- "license": "MIT"
- },
- "node_modules/@esbuild/aix-ppc64": {
- "version": "0.21.5",
- "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz",
- "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==",
- "cpu": [
- "ppc64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "aix"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/@esbuild/android-arm": {
- "version": "0.21.5",
- "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz",
- "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==",
- "cpu": [
- "arm"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "android"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/@esbuild/android-arm64": {
- "version": "0.21.5",
- "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz",
- "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "android"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/@esbuild/android-x64": {
- "version": "0.21.5",
- "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz",
- "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "android"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/@esbuild/darwin-arm64": {
- "version": "0.21.5",
- "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz",
- "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "darwin"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/@esbuild/darwin-x64": {
- "version": "0.21.5",
- "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz",
- "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "darwin"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/@esbuild/freebsd-arm64": {
- "version": "0.21.5",
- "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz",
- "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "freebsd"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/@esbuild/freebsd-x64": {
- "version": "0.21.5",
- "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz",
- "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "freebsd"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/@esbuild/linux-arm": {
- "version": "0.21.5",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz",
- "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==",
- "cpu": [
- "arm"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/@esbuild/linux-arm64": {
- "version": "0.21.5",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz",
- "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/@esbuild/linux-ia32": {
- "version": "0.21.5",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz",
- "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==",
- "cpu": [
- "ia32"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/@esbuild/linux-loong64": {
- "version": "0.21.5",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz",
- "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==",
- "cpu": [
- "loong64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/@esbuild/linux-mips64el": {
- "version": "0.21.5",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz",
- "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==",
- "cpu": [
- "mips64el"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/@esbuild/linux-ppc64": {
- "version": "0.21.5",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz",
- "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==",
- "cpu": [
- "ppc64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/@esbuild/linux-riscv64": {
- "version": "0.21.5",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz",
- "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==",
- "cpu": [
- "riscv64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/@esbuild/linux-s390x": {
- "version": "0.21.5",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz",
- "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==",
- "cpu": [
- "s390x"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/@esbuild/linux-x64": {
- "version": "0.21.5",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz",
- "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/@esbuild/netbsd-x64": {
- "version": "0.21.5",
- "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz",
- "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "netbsd"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/@esbuild/openbsd-x64": {
- "version": "0.21.5",
- "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz",
- "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "openbsd"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/@esbuild/sunos-x64": {
- "version": "0.21.5",
- "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz",
- "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==",
- "cpu": [
- "x64"
+ },
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/csstools"
+ }
],
- "dev": true,
"license": "MIT",
- "optional": true,
- "os": [
- "sunos"
- ],
+ "dependencies": {
+ "@csstools/color-helpers": "^6.0.2",
+ "@csstools/css-calc": "^3.2.0"
+ },
"engines": {
- "node": ">=12"
+ "node": ">=20.19.0"
+ },
+ "peerDependencies": {
+ "@csstools/css-parser-algorithms": "^4.0.0",
+ "@csstools/css-tokenizer": "^4.0.0"
}
},
- "node_modules/@esbuild/win32-arm64": {
- "version": "0.21.5",
- "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz",
- "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==",
- "cpu": [
- "arm64"
- ],
+ "node_modules/@csstools/css-parser-algorithms": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-4.0.0.tgz",
+ "integrity": "sha512-+B87qS7fIG3L5h3qwJ/IFbjoVoOe/bpOdh9hAjXbvx0o8ImEmUsGXN0inFOnk2ChCFgqkkGFQ+TpM5rbhkKe4w==",
"dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "win32"
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/csstools"
+ },
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/csstools"
+ }
],
+ "license": "MIT",
"engines": {
- "node": ">=12"
+ "node": ">=20.19.0"
+ },
+ "peerDependencies": {
+ "@csstools/css-tokenizer": "^4.0.0"
}
},
- "node_modules/@esbuild/win32-ia32": {
- "version": "0.21.5",
- "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz",
- "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==",
- "cpu": [
- "ia32"
- ],
+ "node_modules/@csstools/css-syntax-patches-for-csstree": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/@csstools/css-syntax-patches-for-csstree/-/css-syntax-patches-for-csstree-1.1.3.tgz",
+ "integrity": "sha512-SH60bMfrRCJF3morcdk57WklujF4Jr/EsQUzqkarfHXEFcAR1gg7fS/chAE922Sehgzc1/+Tz5H3Ypa1HiEKrg==",
"dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "win32"
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/csstools"
+ },
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/csstools"
+ }
],
- "engines": {
- "node": ">=12"
+ "license": "MIT-0",
+ "peerDependencies": {
+ "css-tree": "^3.2.1"
+ },
+ "peerDependenciesMeta": {
+ "css-tree": {
+ "optional": true
+ }
}
},
- "node_modules/@esbuild/win32-x64": {
- "version": "0.21.5",
- "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz",
- "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==",
- "cpu": [
- "x64"
- ],
+ "node_modules/@csstools/css-tokenizer": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-4.0.0.tgz",
+ "integrity": "sha512-QxULHAm7cNu72w97JUNCBFODFaXpbDg+dP8b/oWFAZ2MTRppA3U00Y2L1HqaS4J6yBqxwa/Y3nMBaxVKbB/NsA==",
"dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "win32"
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/csstools"
+ },
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/csstools"
+ }
],
+ "license": "MIT",
"engines": {
- "node": ">=12"
+ "node": ">=20.19.0"
}
},
+ "node_modules/@emotion/hash": {
+ "version": "0.8.0",
+ "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.8.0.tgz",
+ "integrity": "sha512-kBJtf7PH6aWwZ6fka3zQ0p6SBYzx4fl1LoZXE2RrnYST9Xljm7WfKJrU4g/Xr3Beg72MLrp1AWNUmuYJTL7Cow==",
+ "license": "MIT"
+ },
+ "node_modules/@emotion/unitless": {
+ "version": "0.7.5",
+ "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.7.5.tgz",
+ "integrity": "sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg==",
+ "license": "MIT"
+ },
"node_modules/@eslint-community/eslint-utils": {
"version": "4.9.1",
"resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz",
@@ -1112,9 +444,9 @@
}
},
"node_modules/@eslint/eslintrc/node_modules/brace-expansion": {
- "version": "1.1.14",
- "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz",
- "integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==",
+ "version": "1.1.18",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.18.tgz",
+ "integrity": "sha512-Edep/X9fGqVNmzKBVsDYIOtD+z1tuezV70LBjdCst9Tqu76lsnvRiZ6oTic1n+/BIwX6QDGAO94PN4N2SADvtw==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -1180,9 +512,9 @@
}
},
"node_modules/@humanwhocodes/config-array/node_modules/brace-expansion": {
- "version": "1.1.14",
- "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz",
- "integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==",
+ "version": "1.1.18",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.18.tgz",
+ "integrity": "sha512-Edep/X9fGqVNmzKBVsDYIOtD+z1tuezV70LBjdCst9Tqu76lsnvRiZ6oTic1n+/BIwX6QDGAO94PN4N2SADvtw==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -1225,69 +557,13 @@
"dev": true,
"license": "BSD-3-Clause"
},
- "node_modules/@jest/schemas": {
- "version": "29.6.3",
- "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz",
- "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@sinclair/typebox": "^0.27.8"
- },
- "engines": {
- "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
- }
- },
- "node_modules/@jridgewell/gen-mapping": {
- "version": "0.3.13",
- "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz",
- "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@jridgewell/sourcemap-codec": "^1.5.0",
- "@jridgewell/trace-mapping": "^0.3.24"
- }
- },
- "node_modules/@jridgewell/remapping": {
- "version": "2.3.5",
- "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz",
- "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@jridgewell/gen-mapping": "^0.3.5",
- "@jridgewell/trace-mapping": "^0.3.24"
- }
- },
- "node_modules/@jridgewell/resolve-uri": {
- "version": "3.1.2",
- "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
- "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=6.0.0"
- }
- },
"node_modules/@jridgewell/sourcemap-codec": {
- "version": "1.5.5",
- "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
- "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==",
+ "version": "1.6.0",
+ "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.6.0.tgz",
+ "integrity": "sha512-T7jf+5zgsZHwNJ4lvQ7/aezbyk0nNX+zJVWpmHA7VYsEx7a7qr5Rg5IbtJFqkgze5Y2sruq1RUY8Q837Od7iFw==",
"dev": true,
"license": "MIT"
},
- "node_modules/@jridgewell/trace-mapping": {
- "version": "0.3.31",
- "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz",
- "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@jridgewell/resolve-uri": "^3.1.0",
- "@jridgewell/sourcemap-codec": "^1.4.14"
- }
- },
"node_modules/@nodelib/fs.scandir": {
"version": "2.1.5",
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
@@ -1326,6 +602,16 @@
"node": ">= 8"
}
},
+ "node_modules/@oxc-project/types": {
+ "version": "0.147.0",
+ "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.147.0.tgz",
+ "integrity": "sha512-IJ3s6ltHLp45S0bh7phkX+gJO7A1Wuz2EaqpAhb8WjqDwbzMiWKHhyyT42tskaWjEYXtHtVCPpnBJVT9+dcRLg==",
+ "dev": true,
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/Boshen"
+ }
+ },
"node_modules/@polka/url": {
"version": "1.0.0-next.29",
"resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.29.tgz",
@@ -2053,25 +1339,18 @@
}
},
"node_modules/@remix-run/router": {
- "version": "1.23.2",
- "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.23.2.tgz",
- "integrity": "sha512-Ic6m2U/rMjTkhERIa/0ZtXJP17QUi2CbWE7cqx4J58M8aA3QTfW+2UlQ4psvTX9IO1RfNVhK3pcpdjej7L+t2w==",
+ "version": "1.23.4",
+ "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.23.4.tgz",
+ "integrity": "sha512-q7j5geK7xs3UJSdm9/iytUNclBnLmYx1EnSeCFXHPeutdqgIMeFeHtUZgS3EhlKxdBEAu8OwtJCwmLrEzpSs7Q==",
"license": "MIT",
"engines": {
"node": ">=14.0.0"
}
},
- "node_modules/@rolldown/pluginutils": {
- "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,
- "license": "MIT"
- },
- "node_modules/@rollup/rollup-android-arm-eabi": {
- "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==",
+ "node_modules/@rolldown/binding-android-arm-eabi": {
+ "version": "1.2.6",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm-eabi/-/binding-android-arm-eabi-1.2.6.tgz",
+ "integrity": "sha512-b+jTcARdTiFLI6jB4a5XjTm0RWd6KcRfQj/I2356fxUZemiho9zQLxo0RtCuMDAyKcLo6cEltkgbQp6d1+sjjQ==",
"cpu": [
"arm"
],
@@ -2080,12 +1359,15 @@
"optional": true,
"os": [
"android"
- ]
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
},
- "node_modules/@rollup/rollup-android-arm64": {
- "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==",
+ "node_modules/@rolldown/binding-android-arm64": {
+ "version": "1.2.6",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.2.6.tgz",
+ "integrity": "sha512-lkWU8ZJaRk9q3CIEY1Tc7vIFALp3Xw5NfGJo2hQg5oIqNgxWi1zI+IiDEK3r70BF5Dzol1tcXsnzsRc8NLhG+Q==",
"cpu": [
"arm64"
],
@@ -2094,12 +1376,15 @@
"optional": true,
"os": [
"android"
- ]
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
},
- "node_modules/@rollup/rollup-darwin-arm64": {
- "version": "4.60.2",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.60.2.tgz",
- "integrity": "sha512-UwRE7CGpvSVEQS8gUMBe1uADWjNnVgP3Iusyda1nSRwNDCsRjnGc7w6El6WLQsXmZTbLZx9cecegumcitNfpmA==",
+ "node_modules/@rolldown/binding-darwin-arm64": {
+ "version": "1.2.6",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.2.6.tgz",
+ "integrity": "sha512-dgR56NYnvAszm7Ob1B2/Vn0e8bUQYZH2UjVaMMtMVOCKFSfjhfLmuA/9+O+F+ajUdG6B/bSssrKW6JJYASa8jA==",
"cpu": [
"arm64"
],
@@ -2108,12 +1393,15 @@
"optional": true,
"os": [
"darwin"
- ]
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
},
- "node_modules/@rollup/rollup-darwin-x64": {
- "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==",
+ "node_modules/@rolldown/binding-darwin-x64": {
+ "version": "1.2.6",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.2.6.tgz",
+ "integrity": "sha512-vpVxFvUCFioJqug7OTvqptkc4yb8UX0AwfDmJpaR/0sWz+BUmqSVAf7c8JkUgnN8YLspb4a/N6NhTyMAmdyQ7Q==",
"cpu": [
"x64"
],
@@ -2122,26 +1410,15 @@
"optional": true,
"os": [
"darwin"
- ]
- },
- "node_modules/@rollup/rollup-freebsd-arm64": {
- "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"
- ]
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
},
- "node_modules/@rollup/rollup-freebsd-x64": {
- "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==",
+ "node_modules/@rolldown/binding-freebsd-x64": {
+ "version": "1.2.6",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.2.6.tgz",
+ "integrity": "sha512-h1wG6Y6K3JlRswxsI64qQJqBAy4vrLuHgRbc8CZMGSWTOFRY6ghMApM1NKzB2I0n5xV1fjkE18SuVl2QpLeNpA==",
"cpu": [
"x64"
],
@@ -2150,26 +1427,15 @@
"optional": true,
"os": [
"freebsd"
- ]
- },
- "node_modules/@rollup/rollup-linux-arm-gnueabihf": {
- "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"
- ]
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
},
- "node_modules/@rollup/rollup-linux-arm-musleabihf": {
- "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==",
+ "node_modules/@rolldown/binding-linux-arm-gnueabihf": {
+ "version": "1.2.6",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.2.6.tgz",
+ "integrity": "sha512-tbCiqub0q2MVWJKgF5PoAlNWCtQydiOYSLIkd8sByqK/6MMYLJRcSXSYodqYtd0O+Fw7QaVmKKlS4oL94YRZ0w==",
"cpu": [
"arm"
],
@@ -2178,26 +1444,15 @@
"optional": true,
"os": [
"linux"
- ]
- },
- "node_modules/@rollup/rollup-linux-arm64-gnu": {
- "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"
- ]
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
},
- "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==",
+ "node_modules/@rolldown/binding-linux-arm64-gnu": {
+ "version": "1.2.6",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.2.6.tgz",
+ "integrity": "sha512-oxK9+baEBPhZG5HB4URY+uU04zJWeZlH6Tb9rB5DK4DF9XR1uXNLXt5Q5ZsugTKayNCNLhkcwz/ye74hRI98dg==",
"cpu": [
"arm64"
],
@@ -2206,54 +1461,32 @@
"optional": true,
"os": [
"linux"
- ]
- },
- "node_modules/@rollup/rollup-linux-loong64-gnu": {
- "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"
- ]
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
},
- "node_modules/@rollup/rollup-linux-loong64-musl": {
- "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==",
+ "node_modules/@rolldown/binding-linux-arm64-musl": {
+ "version": "1.2.6",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.2.6.tgz",
+ "integrity": "sha512-muWCk27FVBEZtv0MsK8gnfSmgczA8KQ0uRVJbTABKhkRfQc38aUrcb7fhi3BNiyseFmgcRsoMfQsSNJ+DbZdSw==",
"cpu": [
- "loong64"
+ "arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
- ]
- },
- "node_modules/@rollup/rollup-linux-ppc64-gnu": {
- "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"
- ]
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
},
- "node_modules/@rollup/rollup-linux-ppc64-musl": {
- "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==",
+ "node_modules/@rolldown/binding-linux-ppc64-gnu": {
+ "version": "1.2.6",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.2.6.tgz",
+ "integrity": "sha512-eWDoSfU7Co2qj3vgB3Dt4lj1mG6CoWbcJQkRMP3XJplyCMtuaq3LHvPFjS9QIPvMGWVadJC04Xiy0IdcVPtnwQ==",
"cpu": [
"ppc64"
],
@@ -2262,40 +1495,15 @@
"optional": true,
"os": [
"linux"
- ]
- },
- "node_modules/@rollup/rollup-linux-riscv64-gnu": {
- "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"
- ]
- },
- "node_modules/@rollup/rollup-linux-riscv64-musl": {
- "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"
- ]
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
},
- "node_modules/@rollup/rollup-linux-s390x-gnu": {
- "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==",
+ "node_modules/@rolldown/binding-linux-s390x-gnu": {
+ "version": "1.2.6",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.2.6.tgz",
+ "integrity": "sha512-2bWNjRSIayvupRKxXUY2tWG9fYdoUlTqWywHRvE8Eq3GvuQ+f2HeIkve697fIt+IQs/PV8yFsdWuhp1aJ1PdnA==",
"cpu": [
"s390x"
],
@@ -2304,12 +1512,15 @@
"optional": true,
"os": [
"linux"
- ]
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
},
- "node_modules/@rollup/rollup-linux-x64-gnu": {
- "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==",
+ "node_modules/@rolldown/binding-linux-x64-gnu": {
+ "version": "1.2.6",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.2.6.tgz",
+ "integrity": "sha512-KekI0gS0wLxe1UBSQSjenBVwou/JkcQPDzBPICGZjxUv9k3RteHDPBQaiOicZUFKRIH2wKEimGwVpnJsbPzu7w==",
"cpu": [
"x64"
],
@@ -2318,12 +1529,15 @@
"optional": true,
"os": [
"linux"
- ]
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
},
- "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==",
+ "node_modules/@rolldown/binding-linux-x64-musl": {
+ "version": "1.2.6",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.2.6.tgz",
+ "integrity": "sha512-TvtPnfVr+HtyGiDmPK4VWmlNm7QhNNAcK5Q9A7aOXsI8545yCyaoMaicXrFZ72JzeYjaUVk7yT243zT0jzjFKQ==",
"cpu": [
"x64"
],
@@ -2332,26 +1546,15 @@
"optional": true,
"os": [
"linux"
- ]
- },
- "node_modules/@rollup/rollup-openbsd-x64": {
- "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"
- ]
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
},
- "node_modules/@rollup/rollup-openharmony-arm64": {
- "version": "4.60.2",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.60.2.tgz",
- "integrity": "sha512-NetAg5iO2uN7eB8zE5qrZ3CSil+7IJt4WDFLcC75Ymywq1VZVD6qJ6EvNLjZ3rEm6gB7XW5JdT60c6MN35Z85Q==",
+ "node_modules/@rolldown/binding-openharmony-arm64": {
+ "version": "1.2.6",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.2.6.tgz",
+ "integrity": "sha512-iOo0VEay2XFhaCcH0sps5XIimkSuOnNaZrf6+ZkoSOQBJPKNU48RkmJv0/lSpipexu5P+ouFgafe5IGr/DiQfg==",
"cpu": [
"arm64"
],
@@ -2360,12 +1563,15 @@
"optional": true,
"os": [
"openharmony"
- ]
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
},
- "node_modules/@rollup/rollup-win32-arm64-msvc": {
- "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==",
+ "node_modules/@rolldown/binding-win32-arm64-msvc": {
+ "version": "1.2.6",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.2.6.tgz",
+ "integrity": "sha512-y5NTmmasMS455JlOCO4ZM9krIchv3Mvm1crL1iUPGOPgEzSkves9n0SdC5Sjz6+qWDFhd8/JpfWMH8NSWNHe+A==",
"cpu": [
"arm64"
],
@@ -2374,26 +1580,15 @@
"optional": true,
"os": [
"win32"
- ]
- },
- "node_modules/@rollup/rollup-win32-ia32-msvc": {
- "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"
- ]
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
},
- "node_modules/@rollup/rollup-win32-x64-gnu": {
- "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==",
+ "node_modules/@rolldown/binding-win32-x64-msvc": {
+ "version": "1.2.6",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.2.6.tgz",
+ "integrity": "sha512-np8iZSLfXlAD4kWhiyq/u0Yt8oZDtRQ8lGhQaCXo2rl37KNjeU0GjJuwr4P3oeZ++ROfofsKNBqR5LTO8aXyWQ==",
"cpu": [
"x64"
],
@@ -2402,26 +1597,22 @@
"optional": true,
"os": [
"win32"
- ]
- },
- "node_modules/@rollup/rollup-win32-x64-msvc": {
- "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"
],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/pluginutils": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.1.tgz",
+ "integrity": "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==",
"dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "win32"
- ]
+ "license": "MIT"
},
- "node_modules/@sinclair/typebox": {
- "version": "0.27.8",
- "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz",
- "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==",
+ "node_modules/@standard-schema/spec": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz",
+ "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==",
"dev": true,
"license": "MIT"
},
@@ -2569,55 +1760,28 @@
"dev": true,
"license": "MIT"
},
- "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,
- "license": "MIT",
- "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,
- "license": "MIT",
- "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==",
+ "node_modules/@types/chai": {
+ "version": "5.2.3",
+ "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz",
+ "integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@babel/parser": "^7.1.0",
- "@babel/types": "^7.0.0"
+ "@types/deep-eql": "*",
+ "assertion-error": "^2.0.1"
}
},
- "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==",
+ "node_modules/@types/deep-eql": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz",
+ "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==",
"dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/types": "^7.28.2"
- }
+ "license": "MIT"
},
"node_modules/@types/estree": {
- "version": "1.0.8",
- "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz",
- "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==",
+ "version": "1.0.9",
+ "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz",
+ "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==",
"dev": true,
"license": "MIT"
},
@@ -2910,221 +2074,170 @@
"license": "ISC"
},
"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==",
+ "version": "6.1.1",
+ "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-6.1.1.tgz",
+ "integrity": "sha512-yxLaQV9gkhS8ezJqCM6+ndU7mDY6gqAg75NQ+0IjwEI8IYOmQCgkRwHKVSfWXW076DsqMo0Dk+0FK1U+M5RgFw==",
"dev": true,
"license": "MIT",
"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"
+ "@rolldown/pluginutils": "^1.0.1"
},
"engines": {
- "node": "^14.18.0 || >=16.0.0"
+ "node": "^20.19.0 || >=22.12.0"
},
"peerDependencies": {
- "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0"
+ "@rolldown/plugin-babel": "^0.1.7 || ^0.2.0",
+ "babel-plugin-react-compiler": "^1.0.0",
+ "oxc-transform-react": "^0.145.0",
+ "vite": "^8.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@rolldown/plugin-babel": {
+ "optional": true
+ },
+ "babel-plugin-react-compiler": {
+ "optional": true
+ },
+ "oxc-transform-react": {
+ "optional": true
+ }
}
},
"node_modules/@vitest/expect": {
- "version": "1.6.1",
- "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-1.6.1.tgz",
- "integrity": "sha512-jXL+9+ZNIJKruofqXuuTClf44eSpcHlgj3CiuNihUF3Ioujtmc0zIa3UJOW5RjDK1YLBJZnWBlPuqhYycLioog==",
+ "version": "4.1.11",
+ "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.11.tgz",
+ "integrity": "sha512-VX2x5vNJXET47KAFzwERI+KRMtTTCSWTfSMKsW7JsUsXV4psq++e3DvZpuTDOpHcxytiDs6p2nhVb2tVDiiUYw==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@vitest/spy": "1.6.1",
- "@vitest/utils": "1.6.1",
- "chai": "^4.3.10"
+ "@standard-schema/spec": "^1.1.0",
+ "@types/chai": "^5.2.2",
+ "@vitest/spy": "4.1.11",
+ "@vitest/utils": "4.1.11",
+ "chai": "^6.2.2",
+ "tinyrainbow": "^3.1.0"
},
"funding": {
"url": "https://opencollective.com/vitest"
}
},
- "node_modules/@vitest/runner": {
- "version": "1.6.1",
- "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-1.6.1.tgz",
- "integrity": "sha512-3nSnYXkVkf3mXFfE7vVyPmi3Sazhb/2cfZGGs0JRzFsPFvAMBEcrweV1V1GsrstdXeKCTXlJbvnQwGWgEIHmOA==",
+ "node_modules/@vitest/mocker": {
+ "version": "4.1.11",
+ "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.11.tgz",
+ "integrity": "sha512-2XJVD55d1o5AZous5CCGKS74g/riOj9odEt2bQpCVZeblHyHdnMeFl4jl0XjU21stf4mbjUkew2eXQZt65g5CQ==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@vitest/utils": "1.6.1",
- "p-limit": "^5.0.0",
- "pathe": "^1.1.1"
+ "@vitest/spy": "4.1.11",
+ "estree-walker": "^3.0.3",
+ "magic-string": "^0.30.21"
},
"funding": {
"url": "https://opencollective.com/vitest"
- }
- },
- "node_modules/@vitest/runner/node_modules/p-limit": {
- "version": "5.0.0",
- "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-5.0.0.tgz",
- "integrity": "sha512-/Eaoq+QyLSiXQ4lyYV23f14mZRQcXnxfHrN0vCai+ak9G0pp9iEQukIIZq5NccEvwRB8PUnZT0KsOoDCINS1qQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "yocto-queue": "^1.0.0"
},
- "engines": {
- "node": ">=18"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/@vitest/runner/node_modules/yocto-queue": {
- "version": "1.2.2",
- "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.2.2.tgz",
- "integrity": "sha512-4LCcse/U2MHZ63HAJVE+v71o7yOdIe4cZ70Wpf8D/IyjDKYQLV5GD46B+hSTjJsvV5PztjvHoU580EftxjDZFQ==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=12.20"
+ "peerDependencies": {
+ "msw": "^2.4.9",
+ "vite": "^6.0.0 || ^7.0.0 || ^8.0.0"
},
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
+ "peerDependenciesMeta": {
+ "msw": {
+ "optional": true
+ },
+ "vite": {
+ "optional": true
+ }
}
},
- "node_modules/@vitest/snapshot": {
- "version": "1.6.1",
- "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-1.6.1.tgz",
- "integrity": "sha512-WvidQuWAzU2p95u8GAKlRMqMyN1yOJkGHnx3M1PL9Raf7AQ1kwLKg04ADlCa3+OXUZE7BceOhVZiuWAbzCKcUQ==",
+ "node_modules/@vitest/pretty-format": {
+ "version": "4.1.11",
+ "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.11.tgz",
+ "integrity": "sha512-yiZzPbGTS9Sr/JpFl8zHrcIkAofNbFV6k21vIgQN/cY/oxZeXhJv5sc/MBJ5jFKWmWs+oJHw0UXLZjmf931+Vw==",
"dev": true,
"license": "MIT",
"dependencies": {
- "magic-string": "^0.30.5",
- "pathe": "^1.1.1",
- "pretty-format": "^29.7.0"
+ "tinyrainbow": "^3.1.0"
},
"funding": {
"url": "https://opencollective.com/vitest"
}
},
- "node_modules/@vitest/snapshot/node_modules/ansi-styles": {
- "version": "5.2.0",
- "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz",
- "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==",
+ "node_modules/@vitest/runner": {
+ "version": "4.1.11",
+ "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.11.tgz",
+ "integrity": "sha512-LztvUgdwMNJMIkj3hQnnxiC2Xy1zNxq928W/xhjCLaNCzqTZOudjwbQf6v9IntZGPw132i2Lq2rgTRZHD3JHNw==",
"dev": true,
"license": "MIT",
- "engines": {
- "node": ">=10"
+ "dependencies": {
+ "@vitest/utils": "4.1.11",
+ "pathe": "^2.0.3"
},
"funding": {
- "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ "url": "https://opencollective.com/vitest"
}
},
- "node_modules/@vitest/snapshot/node_modules/pretty-format": {
- "version": "29.7.0",
- "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz",
- "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==",
+ "node_modules/@vitest/snapshot": {
+ "version": "4.1.11",
+ "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.11.tgz",
+ "integrity": "sha512-pN7ikn1ON7h8ee4gIAp4AzyK+zBtJPzVbqOgu5LCEh4VaJVbPQcgYQYJIMGQPXVeJJq1fnfazis7a5pFNPahog==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@jest/schemas": "^29.6.3",
- "ansi-styles": "^5.0.0",
- "react-is": "^18.0.0"
+ "@vitest/pretty-format": "4.1.11",
+ "@vitest/utils": "4.1.11",
+ "magic-string": "^0.30.21",
+ "pathe": "^2.0.3"
},
- "engines": {
- "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ "funding": {
+ "url": "https://opencollective.com/vitest"
}
},
- "node_modules/@vitest/snapshot/node_modules/react-is": {
- "version": "18.3.1",
- "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz",
- "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==",
- "dev": true,
- "license": "MIT"
- },
"node_modules/@vitest/spy": {
- "version": "1.6.1",
- "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-1.6.1.tgz",
- "integrity": "sha512-MGcMmpGkZebsMZhbQKkAf9CX5zGvjkBTqf8Zx3ApYWXr3wG+QvEu2eXWfnIIWYSJExIp4V9FCKDEeygzkYrXMw==",
+ "version": "4.1.11",
+ "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.11.tgz",
+ "integrity": "sha512-apNa/prQy2qCeywhnixOHPRCgGNhvg7T4Dapfl1GahLp/R+uhBm5cPyFoNVyqsNd2h1nJxL6BqqdIjiABL60YA==",
"dev": true,
"license": "MIT",
- "dependencies": {
- "tinyspy": "^2.2.0"
- },
"funding": {
"url": "https://opencollective.com/vitest"
}
},
"node_modules/@vitest/ui": {
- "version": "1.6.1",
- "resolved": "https://registry.npmjs.org/@vitest/ui/-/ui-1.6.1.tgz",
- "integrity": "sha512-xa57bCPGuzEFqGjPs3vVLyqareG8DX0uMkr5U/v5vLv5/ZUrBrPL7gzxzTJedEyZxFMfsozwTIbbYfEQVo3kgg==",
+ "version": "4.1.11",
+ "resolved": "https://registry.npmjs.org/@vitest/ui/-/ui-4.1.11.tgz",
+ "integrity": "sha512-r/rwyKoev21mWdRGSEkZOqkQ2BYy68mwjihg9M90nNRbf4NGrgzZ4cj6JNCEwlOGJkbKeMgsjlykvwKUbRr7gw==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@vitest/utils": "1.6.1",
- "fast-glob": "^3.3.2",
- "fflate": "^0.8.1",
- "flatted": "^3.2.9",
- "pathe": "^1.1.1",
- "picocolors": "^1.0.0",
- "sirv": "^2.0.4"
+ "@vitest/utils": "4.1.11",
+ "fflate": "^0.8.2",
+ "flatted": "^3.4.2",
+ "pathe": "^2.0.3",
+ "sirv": "^3.0.2",
+ "tinyglobby": "^0.2.15",
+ "tinyrainbow": "^3.1.0"
},
"funding": {
"url": "https://opencollective.com/vitest"
},
"peerDependencies": {
- "vitest": "1.6.1"
+ "vitest": "4.1.11"
}
},
"node_modules/@vitest/utils": {
- "version": "1.6.1",
- "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-1.6.1.tgz",
- "integrity": "sha512-jOrrUvXM4Av9ZWiG1EajNto0u96kWAhJ1LmPmJhXXQx/32MecEKd10pOLYgS2BQx1TgkGhloPU1ArDW2vvaY6g==",
+ "version": "4.1.11",
+ "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.11.tgz",
+ "integrity": "sha512-zTCVGpyFsGWBhllOyKlTw/vnr6D9qxsfSDyfbyZmTyjHw5N/VuvzHpHoQjm2ZJzn4RJgx5w4r7V0er69CmLgPQ==",
"dev": true,
"license": "MIT",
"dependencies": {
- "diff-sequences": "^29.6.3",
- "estree-walker": "^3.0.3",
- "loupe": "^2.3.7",
- "pretty-format": "^29.7.0"
+ "@vitest/pretty-format": "4.1.11",
+ "convert-source-map": "^2.0.0",
+ "tinyrainbow": "^3.1.0"
},
"funding": {
"url": "https://opencollective.com/vitest"
}
},
- "node_modules/@vitest/utils/node_modules/ansi-styles": {
- "version": "5.2.0",
- "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz",
- "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/chalk/ansi-styles?sponsor=1"
- }
- },
- "node_modules/@vitest/utils/node_modules/pretty-format": {
- "version": "29.7.0",
- "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz",
- "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@jest/schemas": "^29.6.3",
- "ansi-styles": "^5.0.0",
- "react-is": "^18.0.0"
- },
- "engines": {
- "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
- }
- },
- "node_modules/@vitest/utils/node_modules/react-is": {
- "version": "18.3.1",
- "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz",
- "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==",
- "dev": true,
- "license": "MIT"
- },
"node_modules/acorn": {
"version": "8.15.0",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz",
@@ -3148,17 +2261,16 @@
"acorn": "^6.0.0 || ^7.0.0 || ^8.0.0"
}
},
- "node_modules/acorn-walk": {
- "version": "8.3.4",
- "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz",
- "integrity": "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==",
- "dev": true,
+ "node_modules/agent-base": {
+ "version": "6.0.2",
+ "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz",
+ "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==",
"license": "MIT",
"dependencies": {
- "acorn": "^8.11.0"
+ "debug": "4"
},
"engines": {
- "node": ">=0.4.0"
+ "node": ">= 6.0.0"
}
},
"node_modules/ajv": {
@@ -3303,13 +2415,13 @@
}
},
"node_modules/assertion-error": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz",
- "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==",
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz",
+ "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==",
"dev": true,
"license": "MIT",
"engines": {
- "node": "*"
+ "node": ">=12"
}
},
"node_modules/asynckit": {
@@ -3335,13 +2447,14 @@
}
},
"node_modules/axios": {
- "version": "1.16.0",
- "resolved": "https://registry.npmjs.org/axios/-/axios-1.16.0.tgz",
- "integrity": "sha512-6hp5CwvTPlN2A31g5dxnwAX0orzM7pmCRDLnZSX772mv8WDqICwFjowHuPs04Mc8deIld1+ejhtaMn5vp6b+1w==",
+ "version": "1.20.0",
+ "resolved": "https://registry.npmjs.org/axios/-/axios-1.20.0.tgz",
+ "integrity": "sha512-r8aOh8j9cGKpgQAqpzrUHnSIc6a59Y3Xf/cv8sy1DrHCkZHzQGEuoq1tARk6qSyDdtQGSDgpb9kFlruzPvrgwg==",
"license": "MIT",
"dependencies": {
"follow-redirects": "^1.16.0",
- "form-data": "^4.0.5",
+ "form-data": "^4.0.6",
+ "https-proxy-agent": "^5.0.1",
"proxy-from-env": "^2.1.0"
}
},
@@ -3352,16 +2465,6 @@
"dev": true,
"license": "MIT"
},
- "node_modules/baseline-browser-mapping": {
- "version": "2.9.19",
- "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.19.tgz",
- "integrity": "sha512-ipDqC8FrAl/76p2SSWKSI+H9tFwm7vYqXQrItCuiVPt26Km0jS+NzSsBWAaBusvSbQcfJG+JitdMm+wZAgTYqg==",
- "dev": true,
- "license": "Apache-2.0",
- "bin": {
- "baseline-browser-mapping": "dist/cli.js"
- }
- },
"node_modules/bidi-js": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/bidi-js/-/bidi-js-1.0.3.tgz",
@@ -3373,15 +2476,15 @@
}
},
"node_modules/brace-expansion": {
- "version": "5.0.5",
- "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz",
- "integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==",
+ "version": "5.0.9",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.9.tgz",
+ "integrity": "sha512-ScQ4IuvIEF1TMlP7Zt+vjJ//9zlPb2SDcxWxM3bk8s6t6GGdJ7KO1dCcTidOPJKePW30LE/2cT7wCyPho9/Wxg==",
"license": "MIT",
"dependencies": {
"balanced-match": "^4.0.2"
},
"engines": {
- "node": "18 || 20 || >=22"
+ "node": "20 || >=22"
}
},
"node_modules/brace-expansion/node_modules/balanced-match": {
@@ -3393,63 +2496,6 @@
"node": "18 || 20 || >=22"
}
},
- "node_modules/braces": {
- "version": "3.0.3",
- "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
- "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "fill-range": "^7.1.1"
- },
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/browserslist": {
- "version": "4.28.1",
- "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz",
- "integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==",
- "dev": true,
- "funding": [
- {
- "type": "opencollective",
- "url": "https://opencollective.com/browserslist"
- },
- {
- "type": "tidelift",
- "url": "https://tidelift.com/funding/github/npm/browserslist"
- },
- {
- "type": "github",
- "url": "https://github.com/sponsors/ai"
- }
- ],
- "license": "MIT",
- "dependencies": {
- "baseline-browser-mapping": "^2.9.0",
- "caniuse-lite": "^1.0.30001759",
- "electron-to-chromium": "^1.5.263",
- "node-releases": "^2.0.27",
- "update-browserslist-db": "^1.2.0"
- },
- "bin": {
- "browserslist": "cli.js"
- },
- "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"
- }
- },
"node_modules/call-bind": {
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz",
@@ -3509,44 +2555,14 @@
"node": ">=6"
}
},
- "node_modules/caniuse-lite": {
- "version": "1.0.30001766",
- "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001766.tgz",
- "integrity": "sha512-4C0lfJ0/YPjJQHagaE9x2Elb69CIqEPZeG0anQt9SIvIoOH4a4uaRl73IavyO+0qZh6MDLH//DrXThEYKHkmYA==",
- "dev": true,
- "funding": [
- {
- "type": "opencollective",
- "url": "https://opencollective.com/browserslist"
- },
- {
- "type": "tidelift",
- "url": "https://tidelift.com/funding/github/npm/caniuse-lite"
- },
- {
- "type": "github",
- "url": "https://github.com/sponsors/ai"
- }
- ],
- "license": "CC-BY-4.0"
- },
"node_modules/chai": {
- "version": "4.5.0",
- "resolved": "https://registry.npmjs.org/chai/-/chai-4.5.0.tgz",
- "integrity": "sha512-RITGBfijLkBddZvnn8jdqoTypxvqbOLYQkGGxXzeFjVHvudaPw0HNFD9x928/eUwYWd2dPCugVqspGALTZZQKw==",
+ "version": "6.2.2",
+ "resolved": "https://registry.npmjs.org/chai/-/chai-6.2.2.tgz",
+ "integrity": "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==",
"dev": true,
"license": "MIT",
- "dependencies": {
- "assertion-error": "^1.1.0",
- "check-error": "^1.0.3",
- "deep-eql": "^4.1.3",
- "get-func-name": "^2.0.2",
- "loupe": "^2.3.6",
- "pathval": "^1.1.1",
- "type-detect": "^4.1.0"
- },
"engines": {
- "node": ">=4"
+ "node": ">=18"
}
},
"node_modules/chalk": {
@@ -3566,19 +2582,6 @@
"url": "https://github.com/chalk/chalk?sponsor=1"
}
},
- "node_modules/check-error": {
- "version": "1.0.3",
- "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.3.tgz",
- "integrity": "sha512-iKEoDYaRmd1mxM90a2OEfWhjsjPpYPuQ+lMYsoxB126+t8fw7ySEO48nmDg5COTjxDI65/Y2OWpeEHk3ZOe8zg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "get-func-name": "^2.0.2"
- },
- "engines": {
- "node": "*"
- }
- },
"node_modules/clsx": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz",
@@ -3633,13 +2636,6 @@
"dev": true,
"license": "MIT"
},
- "node_modules/confbox": {
- "version": "0.1.8",
- "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.1.8.tgz",
- "integrity": "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==",
- "dev": true,
- "license": "MIT"
- },
"node_modules/convert-source-map": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz",
@@ -3723,7 +2719,6 @@
"version": "4.4.3",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
"integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
- "dev": true,
"license": "MIT",
"dependencies": {
"ms": "^2.1.3"
@@ -3744,19 +2739,6 @@
"dev": true,
"license": "MIT"
},
- "node_modules/deep-eql": {
- "version": "4.1.4",
- "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-4.1.4.tgz",
- "integrity": "sha512-SUwdGfqdKOwxCPeVYjwSyRpJ7Z+fhpwIAtmCUdZIWZ/YP5R9WAsyuSgpLVDi9bjWoN2LXHNss/dk3urXtdQxGg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "type-detect": "^4.0.0"
- },
- "engines": {
- "node": ">=6"
- }
- },
"node_modules/deep-equal": {
"version": "2.2.3",
"resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-2.2.3.tgz",
@@ -3852,14 +2834,14 @@
"node": ">=6"
}
},
- "node_modules/diff-sequences": {
- "version": "29.6.3",
- "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz",
- "integrity": "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==",
+ "node_modules/detect-libc": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz",
+ "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==",
"dev": true,
- "license": "MIT",
+ "license": "Apache-2.0",
"engines": {
- "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ "node": ">=8"
}
},
"node_modules/doctrine": {
@@ -3896,13 +2878,6 @@
"node": ">= 0.4"
}
},
- "node_modules/electron-to-chromium": {
- "version": "1.5.279",
- "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.279.tgz",
- "integrity": "sha512-0bblUU5UNdOt5G7XqGiJtpZMONma6WAfq9vsFmtn9x1+joAObr6x1chfqyxFSDCAFwFhCQDrqeAr6MYdpwJ9Hg==",
- "dev": true,
- "license": "ISC"
- },
"node_modules/entities": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/entities/-/entities-8.0.0.tgz",
@@ -3955,6 +2930,13 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/es-module-lexer": {
+ "version": "2.3.2",
+ "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.3.2.tgz",
+ "integrity": "sha512-poHGpORABojJJucnV9KbOavETW8lBVnphkW77ER5/BQ5Fz7oXSoCNek7IH3vR5nRjdsEz926ibFYX8KtLQmdyw==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/es-object-atoms": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
@@ -3982,55 +2964,6 @@
"node": ">= 0.4"
}
},
- "node_modules/esbuild": {
- "version": "0.21.5",
- "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz",
- "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==",
- "dev": true,
- "hasInstallScript": true,
- "license": "MIT",
- "bin": {
- "esbuild": "bin/esbuild"
- },
- "engines": {
- "node": ">=12"
- },
- "optionalDependencies": {
- "@esbuild/aix-ppc64": "0.21.5",
- "@esbuild/android-arm": "0.21.5",
- "@esbuild/android-arm64": "0.21.5",
- "@esbuild/android-x64": "0.21.5",
- "@esbuild/darwin-arm64": "0.21.5",
- "@esbuild/darwin-x64": "0.21.5",
- "@esbuild/freebsd-arm64": "0.21.5",
- "@esbuild/freebsd-x64": "0.21.5",
- "@esbuild/linux-arm": "0.21.5",
- "@esbuild/linux-arm64": "0.21.5",
- "@esbuild/linux-ia32": "0.21.5",
- "@esbuild/linux-loong64": "0.21.5",
- "@esbuild/linux-mips64el": "0.21.5",
- "@esbuild/linux-ppc64": "0.21.5",
- "@esbuild/linux-riscv64": "0.21.5",
- "@esbuild/linux-s390x": "0.21.5",
- "@esbuild/linux-x64": "0.21.5",
- "@esbuild/netbsd-x64": "0.21.5",
- "@esbuild/openbsd-x64": "0.21.5",
- "@esbuild/sunos-x64": "0.21.5",
- "@esbuild/win32-arm64": "0.21.5",
- "@esbuild/win32-ia32": "0.21.5",
- "@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,
- "license": "MIT",
- "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",
@@ -4155,9 +3088,9 @@
}
},
"node_modules/eslint/node_modules/brace-expansion": {
- "version": "1.1.14",
- "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz",
- "integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==",
+ "version": "1.1.18",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.18.tgz",
+ "integrity": "sha512-Edep/X9fGqVNmzKBVsDYIOtD+z1tuezV70LBjdCst9Tqu76lsnvRiZ6oTic1n+/BIwX6QDGAO94PN4N2SADvtw==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -4252,66 +3185,22 @@
"node": ">=0.10.0"
}
},
- "node_modules/execa": {
- "version": "8.0.1",
- "resolved": "https://registry.npmjs.org/execa/-/execa-8.0.1.tgz",
- "integrity": "sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==",
+ "node_modules/expect-type": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.4.0.tgz",
+ "integrity": "sha512-KfYbmpRm0VbLjEvVa9yGwCi9GI34xvi7A/HXYWQO65CSD2u3MczUJSuwXKFIxlGsgBQizV9q5J9NHj4VG0n+pA==",
"dev": true,
- "license": "MIT",
- "dependencies": {
- "cross-spawn": "^7.0.3",
- "get-stream": "^8.0.1",
- "human-signals": "^5.0.0",
- "is-stream": "^3.0.0",
- "merge-stream": "^2.0.0",
- "npm-run-path": "^5.1.0",
- "onetime": "^6.0.0",
- "signal-exit": "^4.1.0",
- "strip-final-newline": "^3.0.0"
- },
+ "license": "Apache-2.0",
"engines": {
- "node": ">=16.17"
- },
- "funding": {
- "url": "https://github.com/sindresorhus/execa?sponsor=1"
+ "node": ">=12.0.0"
}
},
"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"
- },
- "node_modules/fast-glob": {
- "version": "3.3.3",
- "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz",
- "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@nodelib/fs.stat": "^2.0.2",
- "@nodelib/fs.walk": "^1.2.3",
- "glob-parent": "^5.1.2",
- "merge2": "^1.3.0",
- "micromatch": "^4.0.8"
- },
- "engines": {
- "node": ">=8.6.0"
- }
- },
- "node_modules/fast-glob/node_modules/glob-parent": {
- "version": "5.1.2",
- "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
- "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
- "dev": true,
- "license": "ISC",
- "dependencies": {
- "is-glob": "^4.0.1"
- },
- "engines": {
- "node": ">= 6"
- }
+ "dev": true,
+ "license": "MIT"
},
"node_modules/fast-json-stable-stringify": {
"version": "2.1.0",
@@ -4338,9 +3227,9 @@
}
},
"node_modules/fflate": {
- "version": "0.8.2",
- "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.8.2.tgz",
- "integrity": "sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==",
+ "version": "0.8.3",
+ "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.8.3.tgz",
+ "integrity": "sha512-tbZNuJrLwGUp3zshBtdy4W+ORxZuIh8a5ilyIEQDC5rY1f3U20JMry0Ll3WBzU58EZKsEuJFXhb5gwv8CsPvgA==",
"dev": true,
"license": "MIT"
},
@@ -4357,19 +3246,6 @@
"node": "^10.12.0 || >=12.0.0"
}
},
- "node_modules/fill-range": {
- "version": "7.1.1",
- "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
- "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "to-regex-range": "^5.0.1"
- },
- "engines": {
- "node": ">=8"
- }
- },
"node_modules/find-up": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz",
@@ -4446,16 +3322,16 @@
}
},
"node_modules/form-data": {
- "version": "4.0.5",
- "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz",
- "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==",
+ "version": "4.0.6",
+ "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.6.tgz",
+ "integrity": "sha512-vKatAh4SlVfgbv+YtmhiRjhEMJsYpsG1Y2rMQtR+SVSbytsSD1YGzDIcrAJmdFec88u/+VoGmxnl+80gL1tRCQ==",
"license": "MIT",
"dependencies": {
"asynckit": "^0.4.0",
"combined-stream": "^1.0.8",
"es-set-tostringtag": "^2.1.0",
- "hasown": "^2.0.2",
- "mime-types": "^2.1.12"
+ "hasown": "^2.0.4",
+ "mime-types": "^2.1.35"
},
"engines": {
"node": ">= 6"
@@ -4502,26 +3378,6 @@
"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,
- "license": "MIT",
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/get-func-name": {
- "version": "2.0.2",
- "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.2.tgz",
- "integrity": "sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": "*"
- }
- },
"node_modules/get-intrinsic": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
@@ -4559,19 +3415,6 @@
"node": ">= 0.4"
}
},
- "node_modules/get-stream": {
- "version": "8.0.1",
- "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-8.0.1.tgz",
- "integrity": "sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=16"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
"node_modules/glob": {
"version": "7.2.3",
"resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
@@ -4608,9 +3451,9 @@
}
},
"node_modules/glob/node_modules/brace-expansion": {
- "version": "1.1.14",
- "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz",
- "integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==",
+ "version": "1.1.18",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.18.tgz",
+ "integrity": "sha512-Edep/X9fGqVNmzKBVsDYIOtD+z1tuezV70LBjdCst9Tqu76lsnvRiZ6oTic1n+/BIwX6QDGAO94PN4N2SADvtw==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -4730,9 +3573,9 @@
}
},
"node_modules/hasown": {
- "version": "2.0.2",
- "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
- "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz",
+ "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==",
"license": "MIT",
"dependencies": {
"function-bind": "^1.1.2"
@@ -4754,14 +3597,17 @@
"node": "^20.19.0 || ^22.12.0 || >=24.0.0"
}
},
- "node_modules/human-signals": {
- "version": "5.0.0",
- "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-5.0.0.tgz",
- "integrity": "sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==",
- "dev": true,
- "license": "Apache-2.0",
+ "node_modules/https-proxy-agent": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz",
+ "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==",
+ "license": "MIT",
+ "dependencies": {
+ "agent-base": "6",
+ "debug": "4"
+ },
"engines": {
- "node": ">=16.17.0"
+ "node": ">= 6"
}
},
"node_modules/ignore": {
@@ -4985,16 +3831,6 @@
"integrity": "sha512-Tz/yndySvLAEXh+Uk8liFCxOwVH6YutuR74utvOcu7I9Di+DwM0mtdPVZNaVvvBUM2OXxne/NhOs1zAO7riusQ==",
"license": "MIT"
},
- "node_modules/is-number": {
- "version": "7.0.0",
- "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
- "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=0.12.0"
- }
- },
"node_modules/is-number-object": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.1.1.tgz",
@@ -5077,19 +3913,6 @@
"url": "https://github.com/sponsors/ljharb"
}
},
- "node_modules/is-stream": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz",
- "integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
"node_modules/is-string": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/is-string/-/is-string-1.1.1.tgz",
@@ -5176,10 +3999,20 @@
"license": "MIT"
},
"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==",
+ "version": "4.3.2",
+ "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.2.tgz",
+ "integrity": "sha512-SFNOvSJ+Dgf/9An904Yx+CgSlIPCkIpao4qo51lpee25TIRejdH3rhR4EZMGoNx3/TP3O+wzWuiTFl4sqbltzA==",
"dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/puzrin"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/nodeca"
+ }
+ ],
"license": "MIT",
"dependencies": {
"argparse": "^2.0.1"
@@ -5239,19 +4072,6 @@
"node": "20 || >=22"
}
},
- "node_modules/jsesc": {
- "version": "3.1.0",
- "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz",
- "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==",
- "dev": true,
- "license": "MIT",
- "bin": {
- "jsesc": "bin/jsesc"
- },
- "engines": {
- "node": ">=6"
- }
- },
"node_modules/json-buffer": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz",
@@ -5282,19 +4102,6 @@
"string-convert": "^0.2.0"
}
},
- "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",
@@ -5319,21 +4126,265 @@
"node": ">= 0.8.0"
}
},
- "node_modules/local-pkg": {
- "version": "0.5.1",
- "resolved": "https://registry.npmjs.org/local-pkg/-/local-pkg-0.5.1.tgz",
- "integrity": "sha512-9rrA30MRRP3gBD3HTGnC6cDFpaE1kVDWxWgqWJUN0RvDNAo+Nz/9GxB+nHOH0ifbVFy0hSA1V6vFDvnx54lTEQ==",
+ "node_modules/lightningcss": {
+ "version": "1.33.0",
+ "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.33.0.tgz",
+ "integrity": "sha512-WkUDrojuJs0xkgGf2udWxa3yGBRxPtxUkB79i6aCZLRgc7PM8fZe9TosfPDcvEpQZbuFASnHYmRLBLUbmLOIIA==",
"dev": true,
- "license": "MIT",
+ "license": "MPL-2.0",
"dependencies": {
- "mlly": "^1.7.3",
- "pkg-types": "^1.2.1"
+ "detect-libc": "^2.0.3"
},
"engines": {
- "node": ">=14"
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ },
+ "optionalDependencies": {
+ "lightningcss-android-arm64": "1.33.0",
+ "lightningcss-darwin-arm64": "1.33.0",
+ "lightningcss-darwin-x64": "1.33.0",
+ "lightningcss-freebsd-x64": "1.33.0",
+ "lightningcss-linux-arm-gnueabihf": "1.33.0",
+ "lightningcss-linux-arm64-gnu": "1.33.0",
+ "lightningcss-linux-arm64-musl": "1.33.0",
+ "lightningcss-linux-x64-gnu": "1.33.0",
+ "lightningcss-linux-x64-musl": "1.33.0",
+ "lightningcss-win32-arm64-msvc": "1.33.0",
+ "lightningcss-win32-x64-msvc": "1.33.0"
+ }
+ },
+ "node_modules/lightningcss-android-arm64": {
+ "version": "1.33.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.33.0.tgz",
+ "integrity": "sha512-gEpRTalKdosp4Bb8qWtc2iOgE5SeIHlpS1up9bFq2wAyYhl1UdTObYiHe98zEM9SQvSoqQZ1IQD0JNpg3Ml5pg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-darwin-arm64": {
+ "version": "1.33.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.33.0.tgz",
+ "integrity": "sha512-Sciaz8eenNTKn9b3t7+xr0ipTp9YxKQY4npwQ3mrRuL0BAVHBLyZxofhaKBAVtzmtRZ/zTyo0/to4B1uWG/Djg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-darwin-x64": {
+ "version": "1.33.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.33.0.tgz",
+ "integrity": "sha512-Z5UPAxzrjlWNNyGy6i65cJzzvgJ5D3T6wMvs+gWpY9d7qRhANrxqAp6LhxIgZhWEw18RfJTGcRxjuLIBr+m8XQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-freebsd-x64": {
+ "version": "1.33.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.33.0.tgz",
+ "integrity": "sha512-QQM/Ti/hQajJwCY+RiWuCZ9sdtI/XQk7nDK5vC8kkdwixezOlDgvDx7+RT+QjK6FcFT4MpsuoBnHIo/O3StRRg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-linux-arm-gnueabihf": {
+ "version": "1.33.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.33.0.tgz",
+ "integrity": "sha512-N7FVBe6iS24MlM6R/4RBTxGhQheZGs7tiQ9U32UtF75NzP5Q7xWPRqLBCKxlRQRk3rY1jCIPLzx7WzOhuUIRLQ==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-linux-arm64-gnu": {
+ "version": "1.33.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.33.0.tgz",
+ "integrity": "sha512-j2v/itmy4HlNxlc6voKXYgBqNi0Ng2LShg4z7GufpEgs05P+2suBVyi9I6YHq5uoVFx9ETin3eCEhLVyXGQnKg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-linux-arm64-musl": {
+ "version": "1.33.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.33.0.tgz",
+ "integrity": "sha512-yiO5ROMuYQgXbC60yjZU5CYSFZGKXL0HFATXt9mHJn1+zW55oCtMI9NfcVhYLMFDL7gV7oBPon/EmMMGg2OvtQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-linux-x64-gnu": {
+ "version": "1.33.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.33.0.tgz",
+ "integrity": "sha512-ar+Ju7LmcN0Jo4FpL4hpFybwNG9/3A/Br5KW2n2jyODg3MEZXaDYADdemoNS+BDNfMgKvylJLj4S5tyRActuAg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-linux-x64-musl": {
+ "version": "1.33.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.33.0.tgz",
+ "integrity": "sha512-RYiYbkokw0trfKqqzfF55lginwEPrD3OJDfTuJzFs1MK6iFnDenaz1fqLLtX4ITG3OktJQXOeTaw1awrBAlZPw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-win32-arm64-msvc": {
+ "version": "1.33.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.33.0.tgz",
+ "integrity": "sha512-1K+MPfLSFVpphzpdbfkhlWk6wBrTObBzS2T6db10PNOZgR9GoVsAWzwNyuhUYYbTp23j+4RrncfujZ4uAzXvwA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-win32-x64-msvc": {
+ "version": "1.33.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.33.0.tgz",
+ "integrity": "sha512-OlEICDx/Xl0FqSp4bry8zFnCvGpig3Gl4gCquvYwHuqJKEC1+n9NgDniFvqHGmMv1ZkqDJrDqKKSykTDX+ehuA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
},
"funding": {
- "url": "https://github.com/sponsors/antfu"
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
}
},
"node_modules/locate-path": {
@@ -5366,29 +4417,9 @@
"license": "MIT",
"dependencies": {
"js-tokens": "^3.0.0 || ^4.0.0"
- },
- "bin": {
- "loose-envify": "cli.js"
- }
- },
- "node_modules/loupe": {
- "version": "2.3.7",
- "resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.7.tgz",
- "integrity": "sha512-zSMINGVYkdpYSOBmLi0D1Uo7JU9nVdQKrHxC8eYlV+9YKK9WePqAlL7lSlorG/U2Fw1w0hTBmaa/jrQ3UbPHtA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "get-func-name": "^2.0.1"
- }
- },
- "node_modules/lru-cache": {
- "version": "5.1.1",
- "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
- "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==",
- "dev": true,
- "license": "ISC",
- "dependencies": {
- "yallist": "^3.0.2"
+ },
+ "bin": {
+ "loose-envify": "cli.js"
}
},
"node_modules/lz-string": {
@@ -5427,37 +4458,6 @@
"dev": true,
"license": "CC0-1.0"
},
- "node_modules/merge-stream": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz",
- "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/merge2": {
- "version": "1.4.1",
- "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
- "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">= 8"
- }
- },
- "node_modules/micromatch": {
- "version": "4.0.8",
- "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz",
- "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "braces": "^3.0.3",
- "picomatch": "^2.3.1"
- },
- "engines": {
- "node": ">=8.6"
- }
- },
"node_modules/mime-db": {
"version": "1.52.0",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
@@ -5479,19 +4479,6 @@
"node": ">= 0.6"
}
},
- "node_modules/mimic-fn": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz",
- "integrity": "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=12"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
"node_modules/min-indent": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz",
@@ -5517,26 +4504,6 @@
"url": "https://github.com/sponsors/isaacs"
}
},
- "node_modules/mlly": {
- "version": "1.8.0",
- "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.8.0.tgz",
- "integrity": "sha512-l8D9ODSRWLe2KHJSifWGwBqpTZXIXTeo8mlKjY+E2HAakaTeNpqAyBZ8GSqLzHgw4XmHmC8whvpjJNMbFZN7/g==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "acorn": "^8.15.0",
- "pathe": "^2.0.3",
- "pkg-types": "^1.3.1",
- "ufo": "^1.6.1"
- }
- },
- "node_modules/mlly/node_modules/pathe": {
- "version": "2.0.3",
- "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz",
- "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==",
- "dev": true,
- "license": "MIT"
- },
"node_modules/mrmime": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/mrmime/-/mrmime-2.0.1.tgz",
@@ -5551,13 +4518,12 @@
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
- "dev": true,
"license": "MIT"
},
"node_modules/nanoid": {
- "version": "3.3.11",
- "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz",
- "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==",
+ "version": "3.3.18",
+ "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.18.tgz",
+ "integrity": "sha512-DTg4MJbGMWkfi6VZFdNt2/caMbQy4Ou+Op/hJQvGEWcnVfoA1QA+xzRKAzw9jD6+GVOOeYr/mIcuDSdug6F6+w==",
"dev": true,
"funding": [
{
@@ -5580,42 +4546,6 @@
"dev": true,
"license": "MIT"
},
- "node_modules/node-releases": {
- "version": "2.0.27",
- "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz",
- "integrity": "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/npm-run-path": {
- "version": "5.3.0",
- "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.3.0.tgz",
- "integrity": "sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "path-key": "^4.0.0"
- },
- "engines": {
- "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/npm-run-path/node_modules/path-key": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz",
- "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=12"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
"node_modules/object-inspect": {
"version": "1.13.4",
"resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz",
@@ -5677,6 +4607,20 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/obug": {
+ "version": "2.1.4",
+ "resolved": "https://registry.npmjs.org/obug/-/obug-2.1.4.tgz",
+ "integrity": "sha512-4a+OsYv9UktOJKE+l1A4OufDgdRF9PifWj+tJnHURo/P+WOxpG4GzUFL9qCalmWauao6ogiG+QvnCovwPoyAWA==",
+ "dev": true,
+ "funding": [
+ "https://github.com/sponsors/sxzz",
+ "https://opencollective.com/debug"
+ ],
+ "license": "MIT",
+ "engines": {
+ "node": ">=12.20.0"
+ }
+ },
"node_modules/once": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
@@ -5687,22 +4631,6 @@
"wrappy": "1"
}
},
- "node_modules/onetime": {
- "version": "6.0.0",
- "resolved": "https://registry.npmjs.org/onetime/-/onetime-6.0.0.tgz",
- "integrity": "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "mimic-fn": "^4.0.0"
- },
- "engines": {
- "node": ">=12"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
"node_modules/optionator": {
"version": "0.9.4",
"resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz",
@@ -5810,22 +4738,12 @@
}
},
"node_modules/pathe": {
- "version": "1.1.2",
- "resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.2.tgz",
- "integrity": "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==",
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz",
+ "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==",
"dev": true,
"license": "MIT"
},
- "node_modules/pathval": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.1.tgz",
- "integrity": "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": "*"
- }
- },
"node_modules/picocolors": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
@@ -5834,37 +4752,18 @@
"license": "ISC"
},
"node_modules/picomatch": {
- "version": "2.3.2",
- "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz",
- "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==",
+ "version": "4.0.7",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.7.tgz",
+ "integrity": "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==",
"dev": true,
"license": "MIT",
"engines": {
- "node": ">=8.6"
+ "node": ">=12"
},
"funding": {
"url": "https://github.com/sponsors/jonschlinkert"
}
},
- "node_modules/pkg-types": {
- "version": "1.3.1",
- "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.3.1.tgz",
- "integrity": "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "confbox": "^0.1.8",
- "mlly": "^1.7.4",
- "pathe": "^2.0.1"
- }
- },
- "node_modules/pkg-types/node_modules/pathe": {
- "version": "2.0.3",
- "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz",
- "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==",
- "dev": true,
- "license": "MIT"
- },
"node_modules/possible-typed-array-names": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz",
@@ -5876,9 +4775,9 @@
}
},
"node_modules/postcss": {
- "version": "8.5.13",
- "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.13.tgz",
- "integrity": "sha512-qif0+jGGZoLWdHey3UFHHWP0H7Gbmsk8T5VEqyYFbWqPr1XqvLGBbk/sl8V5exGmcYJklJOhOQq1pV9IcsiFag==",
+ "version": "8.5.26",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.26.tgz",
+ "integrity": "sha512-u82N74LFzG8ca+dD8puPnplTXoGH4fTPpVGuIbt36G3qvNlkvfD0lEAZSxaly3KX8TS/L1A1gsCEmvKmBcVbkQ==",
"dev": true,
"funding": [
{
@@ -5896,7 +4795,7 @@
],
"license": "MIT",
"dependencies": {
- "nanoid": "^3.3.11",
+ "nanoid": "^3.3.17",
"picocolors": "^1.1.1",
"source-map-js": "^1.2.1"
},
@@ -6014,23 +4913,13 @@
"dev": true,
"license": "MIT"
},
- "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,
- "license": "MIT",
- "engines": {
- "node": ">=0.10.0"
- }
- },
"node_modules/react-router": {
- "version": "6.30.3",
- "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.30.3.tgz",
- "integrity": "sha512-XRnlbKMTmktBkjCLE8/XcZFlnHvr2Ltdr1eJX4idL55/9BbORzyZEaIkBFDhFGCEWBBItsVrDxwx3gnisMitdw==",
+ "version": "6.30.6",
+ "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.30.6.tgz",
+ "integrity": "sha512-5HfK7k5im7LTOB0EqCQmfvy4C13G92Ssj1VTmouTK3AJvyjKTnFuCV0vcMAD/JS+JC4DvDIBRrlAeJIFjh5VWg==",
"license": "MIT",
"dependencies": {
- "@remix-run/router": "1.23.2"
+ "@remix-run/router": "1.23.4"
},
"engines": {
"node": ">=14.0.0"
@@ -6040,13 +4929,13 @@
}
},
"node_modules/react-router-dom": {
- "version": "6.30.3",
- "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.30.3.tgz",
- "integrity": "sha512-pxPcv1AczD4vso7G4Z3TKcvlxK7g7TNt3/FNGMhfqyntocvYKj+GCatfigGDjbLozC4baguJ0ReCigoDJXb0ag==",
+ "version": "6.30.6",
+ "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.30.6.tgz",
+ "integrity": "sha512-0RHKZz7wwffvkU+2MFVT2NnjK44ssLEV+m0CAJaS2Ksmorrwj7WxH00jO0SOCW26/tINUnJHToXblDs33I38YQ==",
"license": "MIT",
"dependencies": {
- "@remix-run/router": "1.23.2",
- "react-router": "6.30.3"
+ "@remix-run/router": "1.23.4",
+ "react-router": "6.30.6"
},
"engines": {
"node": ">=14.0.0"
@@ -6139,49 +5028,38 @@
"url": "https://github.com/sponsors/isaacs"
}
},
- "node_modules/rollup": {
- "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==",
+ "node_modules/rolldown": {
+ "version": "1.2.6",
+ "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.2.6.tgz",
+ "integrity": "sha512-vMM4q3aixf46GiF1Kok8jDPFsEpXgFWGjUHXNkNHNm+Y2adXAG2dbX91jkti3i0ZRsOlcmbuzAz1poObSHCmUA==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@types/estree": "1.0.8"
+ "@oxc-project/types": "=0.147.0",
+ "@rolldown/pluginutils": "^1.0.0"
},
"bin": {
- "rollup": "dist/bin/rollup"
+ "rolldown": "bin/cli.mjs"
},
"engines": {
- "node": ">=18.0.0",
- "npm": ">=8.0.0"
+ "node": "^20.19.0 || >=22.12.0"
},
"optionalDependencies": {
- "@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"
+ "@rolldown/binding-android-arm-eabi": "1.2.6",
+ "@rolldown/binding-android-arm64": "1.2.6",
+ "@rolldown/binding-darwin-arm64": "1.2.6",
+ "@rolldown/binding-darwin-x64": "1.2.6",
+ "@rolldown/binding-freebsd-x64": "1.2.6",
+ "@rolldown/binding-linux-arm-gnueabihf": "1.2.6",
+ "@rolldown/binding-linux-arm64-gnu": "1.2.6",
+ "@rolldown/binding-linux-arm64-musl": "1.2.6",
+ "@rolldown/binding-linux-ppc64-gnu": "1.2.6",
+ "@rolldown/binding-linux-s390x-gnu": "1.2.6",
+ "@rolldown/binding-linux-x64-gnu": "1.2.6",
+ "@rolldown/binding-linux-x64-musl": "1.2.6",
+ "@rolldown/binding-openharmony-arm64": "1.2.6",
+ "@rolldown/binding-win32-arm64-msvc": "1.2.6",
+ "@rolldown/binding-win32-x64-msvc": "1.2.6"
}
},
"node_modules/run-parallel": {
@@ -6410,23 +5288,10 @@
"dev": true,
"license": "ISC"
},
- "node_modules/signal-exit": {
- "version": "4.1.0",
- "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz",
- "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==",
- "dev": true,
- "license": "ISC",
- "engines": {
- "node": ">=14"
- },
- "funding": {
- "url": "https://github.com/sponsors/isaacs"
- }
- },
"node_modules/sirv": {
- "version": "2.0.4",
- "resolved": "https://registry.npmjs.org/sirv/-/sirv-2.0.4.tgz",
- "integrity": "sha512-94Bdh3cC2PKrbgSOUqTiGPWVZeSiXfKOVZNJniWoqrWrRkB1CJzBU3NEbiTsPcYy1lDsANA/THzS+9WBiy5nfQ==",
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/sirv/-/sirv-3.0.2.tgz",
+ "integrity": "sha512-2wcC/oGxHis/BoHkkPwldgiPSYcpZK3JU28WoMVv55yHJgcZ8rlXvuG9iZggz+sU1d4bRgIGASwyWqjxu3FM0g==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -6435,7 +5300,7 @@
"totalist": "^3.0.0"
},
"engines": {
- "node": ">= 10"
+ "node": ">=18"
}
},
"node_modules/source-map-js": {
@@ -6456,9 +5321,9 @@
"license": "MIT"
},
"node_modules/std-env": {
- "version": "3.10.0",
- "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.10.0.tgz",
- "integrity": "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==",
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/std-env/-/std-env-4.2.0.tgz",
+ "integrity": "sha512-oCUKSupKTHX53EyjDtuZQ64pjLJ6yYCtpmEw0goYxtjG9KpbRe8KAsl2tBUGU9DyMcJ0RwJ8GqJAFzMXcXW1Rw==",
"dev": true,
"license": "MIT"
},
@@ -6495,19 +5360,6 @@
"node": ">=8"
}
},
- "node_modules/strip-final-newline": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz",
- "integrity": "sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=12"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
"node_modules/strip-indent": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz",
@@ -6534,26 +5386,6 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
- "node_modules/strip-literal": {
- "version": "2.1.1",
- "resolved": "https://registry.npmjs.org/strip-literal/-/strip-literal-2.1.1.tgz",
- "integrity": "sha512-631UJ6O00eNGfMiWG78ck80dfBab8X6IVFB51jZK5Icd7XAs60Z5y7QdSd/wGIklnWvRbUNloVzhOKKmutxQ6Q==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "js-tokens": "^9.0.1"
- },
- "funding": {
- "url": "https://github.com/sponsors/antfu"
- }
- },
- "node_modules/strip-literal/node_modules/js-tokens": {
- "version": "9.0.1",
- "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-9.0.1.tgz",
- "integrity": "sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==",
- "dev": true,
- "license": "MIT"
- },
"node_modules/stylis": {
"version": "4.3.6",
"resolved": "https://registry.npmjs.org/stylis/-/stylis-4.3.6.tgz",
@@ -6603,10 +5435,20 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/tinyexec": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.3.0.tgz",
+ "integrity": "sha512-QKAl9m8gWWGHV8jZcPeym6j+XULi6tOf1mT83WYJ4Lk2ytW/uwAWkrP0uFsdoYMdueVJ0qs26wZ+23xeB4ibNQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ }
+ },
"node_modules/tinyglobby": {
- "version": "0.2.16",
- "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.16.tgz",
- "integrity": "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==",
+ "version": "0.2.17",
+ "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz",
+ "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -6638,33 +5480,10 @@
}
}
},
- "node_modules/tinyglobby/node_modules/picomatch": {
- "version": "4.0.4",
- "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz",
- "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=12"
- },
- "funding": {
- "url": "https://github.com/sponsors/jonschlinkert"
- }
- },
- "node_modules/tinypool": {
- "version": "0.8.4",
- "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-0.8.4.tgz",
- "integrity": "sha512-i11VH5gS6IFeLY3gMBQ00/MmLncVP7JLXOw1vlgkytLmJK7QnEr7NXf0LBdxfmNPAeyetukOk0bOYrJrFGjYJQ==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=14.0.0"
- }
- },
- "node_modules/tinyspy": {
- "version": "2.2.1",
- "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-2.2.1.tgz",
- "integrity": "sha512-KYad6Vy5VDWV4GH3fjpseMQ/XU2BhIYP7Vzd0LG44qRWm/Yt2WCOTicFdvmgo6gWaqooMQCawTtILVQJupKu7A==",
+ "node_modules/tinyrainbow": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.1.1.tgz",
+ "integrity": "sha512-yau8yJdTt989Mm0Bd/236QnzEiPf2xLLTqUZRUJOo/3CB078LSwzei343DgtJVmfJKJE3TMINY1u42SQsP6mXw==",
"dev": true,
"license": "MIT",
"engines": {
@@ -6691,19 +5510,6 @@
"dev": true,
"license": "MIT"
},
- "node_modules/to-regex-range": {
- "version": "5.0.1",
- "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
- "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "is-number": "^7.0.0"
- },
- "engines": {
- "node": ">=8.0"
- }
- },
"node_modules/totalist": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/totalist/-/totalist-3.0.1.tgz",
@@ -6766,16 +5572,6 @@
"node": ">= 0.8.0"
}
},
- "node_modules/type-detect": {
- "version": "4.1.0",
- "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.1.0.tgz",
- "integrity": "sha512-Acylog8/luQ8L7il+geoSxhEkazvkslg7PSNKOX59mbB9cOveP5aq9h74Y7YU8yDpJwetzQQrfIwtf4Wp4LKcw==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=4"
- }
- },
"node_modules/type-fest": {
"version": "0.20.2",
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz",
@@ -6803,17 +5599,10 @@
"node": ">=14.17"
}
},
- "node_modules/ufo": {
- "version": "1.6.3",
- "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.6.3.tgz",
- "integrity": "sha512-yDJTmhydvl5lJzBmy/hyOAA0d+aqCBuwl818haVdYCRrWV84o7YyeVm4QlVHStqNrrJSTb6jKuFAVqAFsr+K3Q==",
- "dev": true,
- "license": "MIT"
- },
"node_modules/undici": {
- "version": "7.25.0",
- "resolved": "https://registry.npmjs.org/undici/-/undici-7.25.0.tgz",
- "integrity": "sha512-xXnp4kTyor2Zq+J1FfPI6Eq3ew5h6Vl0F/8d9XU5zZQf1tX9s2Su1/3PiMmUANFULpmksxkClamIZcaUqryHsQ==",
+ "version": "7.29.0",
+ "resolved": "https://registry.npmjs.org/undici/-/undici-7.29.0.tgz",
+ "integrity": "sha512-IDxfleLmmbSskfWSUATiN1nfn2rDuvnMOqb5CWR92iIfojA0Ud+ulOAAEQ57LPr9rWmsreUyf5lwyao+7GNNVw==",
"dev": true,
"license": "MIT",
"engines": {
@@ -6827,37 +5616,6 @@
"dev": true,
"license": "MIT"
},
- "node_modules/update-browserslist-db": {
- "version": "1.2.3",
- "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz",
- "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==",
- "dev": true,
- "funding": [
- {
- "type": "opencollective",
- "url": "https://opencollective.com/browserslist"
- },
- {
- "type": "tidelift",
- "url": "https://tidelift.com/funding/github/npm/browserslist"
- },
- {
- "type": "github",
- "url": "https://github.com/sponsors/ai"
- }
- ],
- "license": "MIT",
- "dependencies": {
- "escalade": "^3.2.0",
- "picocolors": "^1.1.1"
- },
- "bin": {
- "update-browserslist-db": "cli.js"
- },
- "peerDependencies": {
- "browserslist": ">= 4.21.0"
- }
- },
"node_modules/uri-js": {
"version": "4.4.1",
"resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz",
@@ -6878,21 +5636,23 @@
}
},
"node_modules/vite": {
- "version": "5.4.21",
- "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.21.tgz",
- "integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==",
+ "version": "8.2.2",
+ "resolved": "https://registry.npmjs.org/vite/-/vite-8.2.2.tgz",
+ "integrity": "sha512-cFKLV/PRgAUlIRm5WjMjJ86jrftzpqcgH+Us+DS8mI3CDNiH30Whrz8uHL3+MOLPAgqbMBAqWdAHAphOAM+z/Q==",
"dev": true,
"license": "MIT",
"dependencies": {
- "esbuild": "^0.21.3",
- "postcss": "^8.4.43",
- "rollup": "^4.20.0"
+ "lightningcss": "^1.33.0",
+ "picomatch": "^4.0.5",
+ "postcss": "^8.5.26",
+ "rolldown": "~1.2.4",
+ "tinyglobby": "^0.2.17"
},
"bin": {
"vite": "bin/vite.js"
},
"engines": {
- "node": "^18.0.0 || >=20.0.0"
+ "node": "^20.19.0 || >=22.12.0"
},
"funding": {
"url": "https://github.com/vitejs/vite?sponsor=1"
@@ -6901,23 +5661,33 @@
"fsevents": "~2.3.3"
},
"peerDependencies": {
- "@types/node": "^18.0.0 || >=20.0.0",
- "less": "*",
- "lightningcss": "^1.21.0",
- "sass": "*",
- "sass-embedded": "*",
- "stylus": "*",
- "sugarss": "*",
- "terser": "^5.4.0"
+ "@types/node": "^20.19.0 || >=22.12.0",
+ "@vitejs/devtools": "^0.4.0 || ^0.5.0",
+ "esbuild": "^0.27.0 || ^0.28.0",
+ "jiti": ">=1.21.0",
+ "less": "^4.0.0",
+ "sass": "^1.70.0",
+ "sass-embedded": "^1.70.0",
+ "stylus": ">=0.54.8",
+ "sugarss": "^5.0.0",
+ "terser": "^5.16.0",
+ "tsx": "^4.8.1",
+ "yaml": "^2.4.2"
},
"peerDependenciesMeta": {
"@types/node": {
"optional": true
},
- "less": {
+ "@vitejs/devtools": {
+ "optional": true
+ },
+ "esbuild": {
"optional": true
},
- "lightningcss": {
+ "jiti": {
+ "optional": true
+ },
+ "less": {
"optional": true
},
"sass": {
@@ -6934,85 +5704,89 @@
},
"terser": {
"optional": true
+ },
+ "tsx": {
+ "optional": true
+ },
+ "yaml": {
+ "optional": true
}
}
},
- "node_modules/vite-node": {
- "version": "1.6.1",
- "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-1.6.1.tgz",
- "integrity": "sha512-YAXkfvGtuTzwWbDSACdJSg4A4DZiAqckWe90Zapc/sEX3XvHcw1NdurM/6od8J207tSDqNbSsgdCacBgvJKFuA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "cac": "^6.7.14",
- "debug": "^4.3.4",
- "pathe": "^1.1.1",
- "picocolors": "^1.0.0",
- "vite": "^5.0.0"
- },
- "bin": {
- "vite-node": "vite-node.mjs"
- },
- "engines": {
- "node": "^18.0.0 || >=20.0.0"
- },
- "funding": {
- "url": "https://opencollective.com/vitest"
- }
- },
"node_modules/vitest": {
- "version": "1.6.1",
- "resolved": "https://registry.npmjs.org/vitest/-/vitest-1.6.1.tgz",
- "integrity": "sha512-Ljb1cnSJSivGN0LqXd/zmDbWEM0RNNg2t1QW/XUhYl/qPqyu7CsqeWtqQXHVaJsecLPuDoak2oJcZN2QoRIOag==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@vitest/expect": "1.6.1",
- "@vitest/runner": "1.6.1",
- "@vitest/snapshot": "1.6.1",
- "@vitest/spy": "1.6.1",
- "@vitest/utils": "1.6.1",
- "acorn-walk": "^8.3.2",
- "chai": "^4.3.10",
- "debug": "^4.3.4",
- "execa": "^8.0.1",
- "local-pkg": "^0.5.0",
- "magic-string": "^0.30.5",
- "pathe": "^1.1.1",
- "picocolors": "^1.0.0",
- "std-env": "^3.5.0",
- "strip-literal": "^2.0.0",
- "tinybench": "^2.5.1",
- "tinypool": "^0.8.3",
- "vite": "^5.0.0",
- "vite-node": "1.6.1",
- "why-is-node-running": "^2.2.2"
+ "version": "4.1.11",
+ "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.11.tgz",
+ "integrity": "sha512-fhACrNXUidIbGSBr5FlbuBkO7VWC1ZyLl0DO4CU2DrQoAPxX84Ysxs+HeGQpii5lZWV1Q4gBZTTu49mF+A6Edw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@vitest/expect": "4.1.11",
+ "@vitest/mocker": "4.1.11",
+ "@vitest/pretty-format": "4.1.11",
+ "@vitest/runner": "4.1.11",
+ "@vitest/snapshot": "4.1.11",
+ "@vitest/spy": "4.1.11",
+ "@vitest/utils": "4.1.11",
+ "es-module-lexer": "^2.0.0",
+ "expect-type": "^1.3.0",
+ "magic-string": "^0.30.21",
+ "obug": "^2.1.1",
+ "pathe": "^2.0.3",
+ "picomatch": "^4.0.3",
+ "std-env": "^4.0.0-rc.1",
+ "tinybench": "^2.9.0",
+ "tinyexec": "^1.0.2",
+ "tinyglobby": "^0.2.15",
+ "tinyrainbow": "^3.1.0",
+ "vite": "^6.0.0 || ^7.0.0 || ^8.0.0",
+ "why-is-node-running": "^2.3.0"
},
"bin": {
"vitest": "vitest.mjs"
},
"engines": {
- "node": "^18.0.0 || >=20.0.0"
+ "node": "^20.0.0 || ^22.0.0 || >=24.0.0"
},
"funding": {
"url": "https://opencollective.com/vitest"
},
"peerDependencies": {
"@edge-runtime/vm": "*",
- "@types/node": "^18.0.0 || >=20.0.0",
- "@vitest/browser": "1.6.1",
- "@vitest/ui": "1.6.1",
+ "@opentelemetry/api": "^1.9.0",
+ "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0",
+ "@vitest/browser-playwright": "4.1.11",
+ "@vitest/browser-preview": "4.1.11",
+ "@vitest/browser-webdriverio": "4.1.11",
+ "@vitest/coverage-istanbul": "4.1.11",
+ "@vitest/coverage-v8": "4.1.11",
+ "@vitest/ui": "4.1.11",
"happy-dom": "*",
- "jsdom": "*"
+ "jsdom": "*",
+ "vite": "^6.0.0 || ^7.0.0 || ^8.0.0"
},
"peerDependenciesMeta": {
"@edge-runtime/vm": {
"optional": true
},
+ "@opentelemetry/api": {
+ "optional": true
+ },
"@types/node": {
"optional": true
},
- "@vitest/browser": {
+ "@vitest/browser-playwright": {
+ "optional": true
+ },
+ "@vitest/browser-preview": {
+ "optional": true
+ },
+ "@vitest/browser-webdriverio": {
+ "optional": true
+ },
+ "@vitest/coverage-istanbul": {
+ "optional": true
+ },
+ "@vitest/coverage-v8": {
"optional": true
},
"@vitest/ui": {
@@ -7023,6 +5797,9 @@
},
"jsdom": {
"optional": true
+ },
+ "vite": {
+ "optional": false
}
}
},
@@ -7202,13 +5979,6 @@
"dev": true,
"license": "MIT"
},
- "node_modules/yallist": {
- "version": "3.1.1",
- "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
- "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==",
- "dev": true,
- "license": "ISC"
- },
"node_modules/yocto-queue": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
diff --git a/frontend/package.json b/frontend/package.json
index ac557ac..a2a1b11 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -33,14 +33,14 @@
"@types/react-dom": "^18.2.17",
"@typescript-eslint/eslint-plugin": "^8.59.1",
"@typescript-eslint/parser": "^8.59.1",
- "@vitejs/plugin-react": "^4.2.1",
- "@vitest/ui": "^1.1.0",
+ "@vitejs/plugin-react": "^6.1.1",
+ "@vitest/ui": "^4.1.11",
"eslint": "^8.57.0",
"eslint-plugin-react-hooks": "^4.6.0",
"eslint-plugin-react-refresh": "^0.4.5",
"jsdom": "^29.0.2",
"typescript": "^5.3.3",
- "vite": "^5.0.8",
- "vitest": "^1.1.0"
+ "vite": "^8.2.2",
+ "vitest": "^4.1.11"
}
}