diff --git a/contracts/cli-envelopes/runtime_info.success.json b/contracts/cli-envelopes/runtime_info.success.json index 006f46c..3cb9a0c 100644 --- a/contracts/cli-envelopes/runtime_info.success.json +++ b/contracts/cli-envelopes/runtime_info.success.json @@ -3,7 +3,7 @@ "command": "runtime-info", "status": "success", "data": { - "cliVersion": "0.2.10", + "cliVersion": "0.2.11", "envelopeSchemaVersion": "1", "supportedCommands": [ "runtime-info", diff --git a/docs/schemax/docs/guide/databricks-asset-bundles.mdx b/docs/schemax/docs/guide/databricks-asset-bundles.mdx index 1e11af2..2e2311c 100644 --- a/docs/schemax/docs/guide/databricks-asset-bundles.mdx +++ b/docs/schemax/docs/guide/databricks-asset-bundles.mdx @@ -164,13 +164,13 @@ resources: tags: managed_by: "schemax" schemax_project: "" - schemax_version: "0.2.10" + schemax_version: "0.2.11" environments: - environment_key: schemax spec: environment_version: "2" dependencies: - - schemaxpy>=0.2.10 + - schemaxpy>=0.2.11 tasks: - task_key: schemax_apply environment_key: schemax @@ -228,7 +228,7 @@ When the DAB job runs, the following happens: 1. **File sync** -- `databricks bundle deploy` uploads your `.schemax/` project files and `resources/schemax_deploy.py` to the workspace. 2. **Job creation** -- The DAB CLI creates (or updates) the job defined in `resources/schemax.yml`. 3. **Job execution** -- When the job runs (either via `databricks bundle run` or a manual trigger), it: - - Spins up a serverless Python environment (version 2) with `schemaxpy>=0.2.10` installed. + - Spins up a serverless Python environment (version 2) with `schemaxpy>=0.2.11` installed. - Executes `resources/schemax_deploy.py` as a `spark_python_task`. - The deploy script locates the `.schemax/` project root (one level up from `resources/`) and invokes `schemax apply` **in-process** to inherit Databricks runtime authentication. - The target environment, warehouse ID, and auto-rollback flag are passed as `parameters` via `sys.argv`. diff --git a/docs/schemax/docs/guide/setup.mdx b/docs/schemax/docs/guide/setup.mdx index 1ff9a68..d9fb62a 100644 --- a/docs/schemax/docs/guide/setup.mdx +++ b/docs/schemax/docs/guide/setup.mdx @@ -32,6 +32,28 @@ Verify: schemax --help ``` +:::tip Using a virtual environment + +We recommend installing `schemaxpy` inside a virtual environment so it doesn't interfere with other Python projects. + +```bash +# uv (fastest) +uv venv .venv && source .venv/bin/activate +uv pip install schemaxpy + +# conda / mamba +conda create -n schemax python=3.11 && conda activate schemax +pip install schemaxpy + +# standard venv +python3 -m venv .venv && source .venv/bin/activate +pip install schemaxpy +``` + +If your IDE can't find the `schemax` command after installing, select the correct Python interpreter: in VS Code / Cursor, open the Command Palette (`Cmd+Shift+P`) → **Python: Select Interpreter** → choose the interpreter inside your virtual environment. + +::: + ## 3. Open a project 1. In VS Code, Cursor, or your editor, **File → Open Folder** diff --git a/docs/schemax/docs/reference/faq.mdx b/docs/schemax/docs/reference/faq.mdx index 54ceb14..2c0af64 100644 --- a/docs/schemax/docs/reference/faq.mdx +++ b/docs/schemax/docs/reference/faq.mdx @@ -40,6 +40,23 @@ Yes. All changes are stored as **operations** in `.schemax/changelog.json`, and Yes. In the VS Code designer, select a **catalog** or **schema** in the tree, then click **Bulk operations** in the detail panel. You can add the same grant (principal + privileges) or tag (name + value) to all objects in that scope—e.g. all tables and views in a schema, or all schemas, tables, and views in a catalog. The generated operations are the same as adding them one by one; they are just batched for convenience. See [Unity Catalog grants — Bulk grants and tags](/docs/guide/unity-catalog-grants/#bulk-grants-and-tags). +## `schemax` command not found / IDE can't resolve imports + +This usually means the CLI is installed in a virtual environment that isn't active, or your IDE is pointing at the wrong Python interpreter. + +1. **Activate your virtual environment** before running `schemax`: + ```bash + # uv / standard venv + source .venv/bin/activate + + # conda / mamba + conda activate schemax + ``` +2. **Select the correct interpreter in your IDE** — In VS Code / Cursor, open the Command Palette (`Cmd+Shift+P`) → **Python: Select Interpreter** → choose the interpreter inside your virtual environment (e.g. `.venv/bin/python` or the conda env path). +3. **uv venv path** — If you used `uv venv`, the default location is `.venv/` in your project directory. Make sure your IDE points to `.venv/bin/python`. + +After selecting the right interpreter, restart the terminal in your IDE and try `schemax --help` again. + ## Can I use the CLI from my own scripts or automation? Yes. The CLI is a standard Python CLI; you can call it from scripts, cron, or other automation. For example: `schemax sql --target dev --output migration.sql` then run the SQL with your own runner, or `schemax apply --target dev --no-interaction` from a pipeline. See [Git and CI/CD setup](/docs/guide/git-and-cicd-setup/). diff --git a/docs/schemax/docs/reference/release-notes.mdx b/docs/schemax/docs/reference/release-notes.mdx index aa4e16a..4debdef 100644 --- a/docs/schemax/docs/reference/release-notes.mdx +++ b/docs/schemax/docs/reference/release-notes.mdx @@ -6,6 +6,26 @@ This page summarizes coordinated SchemaX releases across: - VS Code extension (`schemax-vscode`) - Documentation site (`docs/schemax`) +## 0.2.11 (2026-03-11) + +### Highlights + +- **v5 multi-target architecture** — Projects now support multiple provider targets (e.g., multiple Unity Catalog catalogs or mixed providers) via a `targets` dict in `project.json`. Existing v4 projects auto-migrate on first load. +- **`--scope` CLI parameter** — New flag on `sql`, `validate`, `apply`, and `workspace-state`. Currently plumbed through CLI and interfaces; target-scoped state loading will be enabled in a follow-up release. +- **Provider-specific settings UI** — Project Settings in the VS Code extension now renders per-provider configuration with target tabs for multi-target projects. +- **Renamed `target_name` to `scope`** — Eliminated naming confusion between environment target (`--target dev`) and provider scope (`--scope analytics`). + +### Package versions + +- Python SDK/CLI: `0.2.11` +- VS Code extension: `0.2.11` +- Docs site package: `0.2.11` + +### Changelogs + +- [Python SDK changelog](https://github.com/vb-dbrks/schemax-vscode/blob/main/packages/python-sdk/CHANGELOG.md) +- [VS Code extension changelog](https://github.com/vb-dbrks/schemax-vscode/blob/main/packages/vscode-extension/CHANGELOG.md) + ## 0.2.10 (2026-03-10) ### Highlights diff --git a/docs/schemax/package.json b/docs/schemax/package.json index 72f5e88..ebc78fb 100644 --- a/docs/schemax/package.json +++ b/docs/schemax/package.json @@ -1,6 +1,6 @@ { "name": "schemax-docs", - "version": "0.2.10", + "version": "0.2.11", "private": true, "scripts": { "docusaurus": "docusaurus", diff --git a/package-lock.json b/package-lock.json index b50ae04..6226c8b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "schemax", - "version": "0.2.8", + "version": "0.2.11", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "schemax", - "version": "0.2.8", + "version": "0.2.11", "workspaces": [ "packages/*" ], @@ -3312,6 +3312,16 @@ "integrity": "sha512-wsNOvNMMJ2BY8rC2N2MNBG7yOowV3ov8KlvUE/AiVUlHKTfWsw3OgAOQduX7h0Un6GssKD3aoTVH+TF3DSQwKQ==", "license": "CC-BY-4.0" }, + "node_modules/@vscode/python-extension": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/@vscode/python-extension/-/python-extension-1.0.6.tgz", + "integrity": "sha512-q7KYf+mymM67G0yS6xfhczFwu2teBi5oXHVdNgtCsYhErdSOkwMPtE291SqQahrjTcgKxn7O56i1qb1EQR6o4w==", + "license": "MIT", + "engines": { + "node": ">=22.17.0", + "vscode": "^1.93.0" + } + }, "node_modules/@vscode/vsce": { "version": "3.7.1", "resolved": "https://registry.npmjs.org/@vscode/vsce/-/vsce-3.7.1.tgz", @@ -10673,10 +10683,11 @@ }, "packages/vscode-extension": { "name": "schemax-vscode", - "version": "0.2.8", + "version": "0.2.11", "license": "Apache-2.0", "dependencies": { "@vscode/codicons": "^0.0.36", + "@vscode/python-extension": "^1.0.6", "@vscode/webview-ui-toolkit": "^1.4.0", "react": "^18.2.0", "react-dom": "^18.2.0", diff --git a/package.json b/package.json index 4a7852a..936b647 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "schemax", - "version": "0.2.10", + "version": "0.2.11", "private": true, "description": "SchemaX - Databricks Unity Catalog schema management with version control", "repository": { diff --git a/packages/python-sdk/CHANGELOG.md b/packages/python-sdk/CHANGELOG.md index ea7f08e..666a302 100644 --- a/packages/python-sdk/CHANGELOG.md +++ b/packages/python-sdk/CHANGELOG.md @@ -1,5 +1,24 @@ # Changelog +## [0.2.11] - 2026-03-11 + +### Added + +- **v5 multi-target project schema** — Projects now use a `targets` dict instead of a single `provider` object, enabling multiple provider instances (e.g., multiple Unity Catalog catalogs or mixed providers) in one project. Existing v4 projects auto-migrate to v5 on first load. +- **`--scope` CLI parameter** — New `--scope` flag on `sql`, `validate`, `apply`, and `workspace-state` commands. Currently plumbed through CLI and interfaces; target-scoped state loading will be enabled in a follow-up release. +- **`Operation.scope` field** — Operations now carry a `scope` field to associate them with a specific target in multi-target projects. +- **Provider-specific settings UI** — Project Settings panel renders target-specific configuration per provider type, with tabs for each target in multi-target projects. + +### Changed + +- **Renamed `target_name` to `scope`** — All internal parameters, CLI flags, and Operation fields renamed from `target_name` to `scope` to avoid confusion with `target` (environment). Breaking change for any code using `--target-name` CLI flag or `Operation.target_name` field. +- **New projects created as v5** — `ensure_project_file()` and extension "New Project" flow now emit v5 project schema directly. + +### Fixed + +- **Apply command `target_name` kwarg error** — Fixed `TypeError` where `_build_apply_request()` passed `target_name` to `apply_to_environment()` which didn't accept it, causing `UNEXPECTED_ERROR` instead of proper error codes. +- **Integration tests updated for v5** — All test files updated to use v5 project schema (`project["targets"][scope]` instead of `project["provider"]`). + ## [0.2.10] - 2026-03-10 ### Added diff --git a/packages/python-sdk/pyproject.toml b/packages/python-sdk/pyproject.toml index 7c3d2db..0ce5b5b 100644 --- a/packages/python-sdk/pyproject.toml +++ b/packages/python-sdk/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "schemaxpy" -version = "0.2.10" +version = "0.2.11" description = "Declarative schema management, governance, and migration for Databricks Unity Catalog. Version control schemas, generate SQL, deploy across environments." readme = "README.md" requires-python = ">=3.11" diff --git a/packages/python-sdk/src/schemax/__init__.py b/packages/python-sdk/src/schemax/__init__.py index 2258a17..7d8599e 100644 --- a/packages/python-sdk/src/schemax/__init__.py +++ b/packages/python-sdk/src/schemax/__init__.py @@ -14,8 +14,10 @@ # Provider system exports # Storage V4 exports (latest) from .core.storage import ( + _get_provider_from_target, create_snapshot, ensure_project_file, + get_target_config, load_current_state, read_changelog, read_project, @@ -68,12 +70,14 @@ def generate_diff_operations( old_snap = read_snapshot(workspace_path, from_version) new_snap = read_snapshot(workspace_path, to_version) - # Get provider + # Get provider from default target project = read_project(workspace_path) - provider = ProviderRegistry.get(project["provider"]["type"]) + target_cfg = get_target_config(project) + provider_type = _get_provider_from_target(target_cfg) + provider = ProviderRegistry.get(provider_type) if not provider: - raise ValueError(f"Provider '{project['provider']['type']}' not found") + raise ValueError(f"Provider '{provider_type}' not found") # Generate diff differ = provider.get_state_differ( diff --git a/packages/python-sdk/src/schemax/application/services.py b/packages/python-sdk/src/schemax/application/services.py index d35300e..aed323f 100644 --- a/packages/python-sdk/src/schemax/application/services.py +++ b/packages/python-sdk/src/schemax/application/services.py @@ -55,8 +55,14 @@ def run(self, *, workspace: Path, provider_id: str) -> CommandResult: class ValidateService: """Validate workspace project state.""" - def run(self, *, workspace: Path, json_output: bool = False) -> CommandResult: - valid = validate_project(workspace, json_output=json_output) + def run( + self, + *, + workspace: Path, + json_output: bool = False, + scope: str | None = None, + ) -> CommandResult: + valid = validate_project(workspace, json_output=json_output, scope=scope) code = "valid" if valid else "invalid" return CommandResult(success=valid, code=code, message="Validation completed") @@ -74,6 +80,7 @@ def run( from_version: str | None, to_version: str | None, target_env: str | None, + scope: str | None = None, ) -> CommandResult: sql = generate_sql_migration( workspace=workspace, @@ -82,6 +89,7 @@ def run( _from_version=from_version, _to_version=to_version, target_env=target_env, + scope=scope, ) return CommandResult( success=True, @@ -201,6 +209,7 @@ def run( no_interaction: bool, auto_rollback: bool, execution_mode: str = "remote", + scope: str | None = None, ) -> CommandResult: request = _build_apply_request( workspace, @@ -211,6 +220,7 @@ def run( no_interaction, auto_rollback, execution_mode, + scope=scope, ) result = apply_to_environment(**request) return CommandResult( @@ -417,6 +427,8 @@ def _build_apply_request( no_interaction: bool, auto_rollback: bool, execution_mode: str = "remote", + *, + scope: str | None = None, ) -> dict[str, Any]: """Build kwargs for apply command boundary calls.""" keys = ( @@ -428,6 +440,7 @@ def _build_apply_request( "no_interaction", "auto_rollback", "execution_mode", + "scope", ) values = ( workspace, @@ -438,6 +451,7 @@ def _build_apply_request( no_interaction, auto_rollback, execution_mode, + scope, ) return dict(zip(keys, values, strict=True)) diff --git a/packages/python-sdk/src/schemax/cli.py b/packages/python-sdk/src/schemax/cli.py index 18fd216..7f7df06 100644 --- a/packages/python-sdk/src/schemax/cli.py +++ b/packages/python-sdk/src/schemax/cli.py @@ -338,6 +338,11 @@ def changelog_undo(op_ids: tuple[str, ...], json_output: bool, workspace: str) - "-t", help="Target environment (maps logical catalog names to physical catalog names)", ) +@click.option( + "--scope", + default=None, + help="Target scope (v5 multi-target). Uses defaultTarget if omitted.", +) @click.option("--json", "json_output", is_flag=True, help="Output results as JSON") @click.argument("workspace", type=click.Path(exists=True), required=False, default=".") def sql( @@ -346,6 +351,7 @@ def sql( from_version: str | None, to_version: str | None, target: str | None, + scope: str | None, json_output: bool, workspace: str, ) -> None: @@ -366,6 +372,7 @@ def sql( from_version=from_version, to_version=to_version, target=target, + scope=scope, json_output=json_output, ) if json_output: @@ -417,6 +424,7 @@ def _run_sql_command( from_version: str | None, to_version: str | None, target: str | None, + scope: str | None = None, json_output: bool, ) -> Any: """Execute SQL service call for SQL command.""" @@ -431,6 +439,7 @@ def _run_sql_command( from_version=from_version, to_version=to_version, target_env=target, + scope=scope, ) return sql_service.run( workspace=workspace_path, @@ -439,18 +448,24 @@ def _run_sql_command( from_version=from_version, to_version=to_version, target_env=target, + scope=scope, ) @cli.command() @click.option("--json", "json_output", is_flag=True, help="Output validation results as JSON") +@click.option( + "--scope", + default=None, + help="Target scope (v5 multi-target). Uses defaultTarget if omitted.", +) @click.argument("workspace", type=click.Path(exists=True), required=False, default=".") -def validate(workspace: str, json_output: bool) -> None: +def validate(workspace: str, json_output: bool, scope: str | None) -> None: """Validate .schemax/ project files""" started_at = perf_counter() workspace_path = Path(workspace).resolve() try: - _run_validate_command(workspace_path, json_output, started_at) + _run_validate_command(workspace_path, json_output, started_at, scope=scope) sys.exit(0) except CommandValidationError as e: if json_output: @@ -486,14 +501,20 @@ def validate(workspace: str, json_output: bool) -> None: sys.exit(1) -def _run_validate_command(workspace_path: Path, json_output: bool, started_at: float) -> None: +def _run_validate_command( + workspace_path: Path, + json_output: bool, + started_at: float, + *, + scope: str | None = None, +) -> None: """Run validate command in console or JSON mode.""" if not json_output: - validate_service.run(workspace=workspace_path, json_output=False) + validate_service.run(workspace=workspace_path, json_output=False, scope=scope) return captured = StringIO() with redirect_stdout(captured): - validate_service.run(workspace=workspace_path, json_output=True) + validate_service.run(workspace=workspace_path, json_output=True, scope=scope) payload = _parse_validate_payload(captured.getvalue()) warnings = payload.get("warnings", []) warning_list = warnings if isinstance(warnings, list) else [] @@ -935,6 +956,11 @@ def _print_import_summary(summary: dict[str, Any]) -> None: "--auto-rollback", is_flag=True, help="Automatically rollback on failure (MVP feature!)" ) @click.option("--json", "json_output", is_flag=True, help="Output results as JSON") +@click.option( + "--scope", + default=None, + help="Target scope (v5 multi-target). Uses defaultTarget if omitted.", +) @click.argument("workspace", type=click.Path(exists=True), required=False, default=".") def apply( target: str, @@ -945,6 +971,7 @@ def apply( no_interaction: bool, auto_rollback: bool, json_output: bool, + scope: str | None, workspace: str, ) -> None: """Execute SQL against target environment @@ -980,6 +1007,7 @@ def apply( no_interaction=no_interaction, auto_rollback=auto_rollback, json_output=json_output, + scope=scope, started_at=started_at, ) sys.exit(exit_code) @@ -1029,6 +1057,7 @@ def _run_apply_command( no_interaction: bool, auto_rollback: bool, json_output: bool, + scope: str | None = None, started_at: float, ) -> int: """Run apply service and emit result envelope if requested.""" @@ -1044,6 +1073,7 @@ def _run_apply_command( dry_run=dry_run, no_interaction=no_interaction, auto_rollback=auto_rollback, + scope=scope, ) else: result = apply_service.run( @@ -1055,6 +1085,7 @@ def _run_apply_command( dry_run=dry_run, no_interaction=no_interaction, auto_rollback=auto_rollback, + scope=scope, ) if json_output: if result.success: @@ -1671,12 +1702,13 @@ def _run_workspace_state( validate_dependencies: bool, json_output: bool, payload_mode: str, + scope: str | None = None, ) -> None: """Load provider-resolved workspace state for IDE consumers.""" started_at = perf_counter() project = workspace_repo.read_project(workspace=workspace_path) state, changelog, provider, validation = workspace_repo.load_current_state( - workspace=workspace_path, validate=validate_dependencies + workspace=workspace_path, validate=validate_dependencies, scope=scope ) serialized_ops = [_serialize_operation(op) for op in changelog.get("ops", [])] changelog_payload: dict[str, Any] = { @@ -1687,7 +1719,7 @@ def _run_workspace_state( changelog_payload["ops"] = [] else: changelog_payload["ops"] = serialized_ops - payload = { + payload: dict[str, Any] = { "state": state, "changelog": changelog_payload, "provider": { @@ -1699,7 +1731,8 @@ def _run_workspace_state( "project": { "name": project.get("name"), "latestSnapshot": project.get("latestSnapshot"), - "provider": project.get("provider", {}), + "targets": project.get("targets", {}), + "defaultTarget": project.get("defaultTarget", "default"), }, "validation": validation or {"errors": [], "warnings": []}, } @@ -1729,12 +1762,18 @@ def _run_workspace_state( show_default=True, help="Payload shape for workspace-state transport", ) +@click.option( + "--scope", + default=None, + help="Target scope (v5 multi-target). Uses defaultTarget if omitted.", +) @click.option("--json", "json_output", is_flag=True, help="Output results as JSON") @click.argument("workspace", type=click.Path(exists=True), required=False, default=".") def workspace_state_cmd( workspace: str, validate_dependencies: bool, payload_mode: str, + scope: str | None, json_output: bool, ) -> None: """Emit current workspace state/changelog/provider metadata for extension transport.""" @@ -1746,6 +1785,7 @@ def workspace_state_cmd( validate_dependencies=validate_dependencies, json_output=json_output, payload_mode=payload_mode, + scope=scope, ) except WorkflowValidationError as e: _emit_validation_error( diff --git a/packages/python-sdk/src/schemax/commands/apply.py b/packages/python-sdk/src/schemax/commands/apply.py index ecfa3fa..e0d970a 100644 --- a/packages/python-sdk/src/schemax/commands/apply.py +++ b/packages/python-sdk/src/schemax/commands/apply.py @@ -44,7 +44,7 @@ def read_project(self, *, workspace: Path) -> dict[str, Any]: ... def read_changelog(self, *, workspace: Path) -> dict[str, Any]: ... def get_environment_config( - self, *, project: dict[str, Any], environment: str + self, *, project: dict[str, Any], environment: str, scope: str | None = None ) -> dict[str, Any]: ... def load_current_state(self, *, workspace: Path, validate: bool = False) -> tuple[Any, ...]: ... @@ -75,9 +75,11 @@ def read_changelog(self, *, workspace: Path) -> dict[str, Any]: return self._repository.read_changelog(workspace=workspace) def get_environment_config( - self, *, project: dict[str, Any], environment: str + self, *, project: dict[str, Any], environment: str, scope: str | None = None ) -> dict[str, Any]: - return self._repository.get_environment_config(project=project, environment=environment) + return self._repository.get_environment_config( + project=project, environment=environment, scope=scope + ) def load_current_state(self, *, workspace: Path, validate: bool = False) -> tuple[Any, ...]: return self._repository.load_current_state(workspace=workspace, validate=validate) @@ -113,6 +115,7 @@ def apply_to_environment( auto_rollback: bool = False, execution_mode: str = "remote", workspace_repo: _WorkspaceRepoPort | None = None, + scope: str | None = None, ) -> ExecutionResult: """Apply changes to target environment @@ -130,6 +133,7 @@ def apply_to_environment( auto_rollback: If True, automatically roll back on failed deployment execution_mode: "remote" (SQL warehouse) or "local" (spark.sql) workspace_repo: Optional repository override for tests/injection + scope: Optional v5 multi-target scope (reserved for future use) Returns: ExecutionResult with deployment details @@ -137,6 +141,7 @@ def apply_to_environment( Raises: ApplyError: If apply fails """ + del scope # v5 multi-target: plumbed from CLI, will be used when per-target state loads repository: _WorkspaceRepoPort = workspace_repo or _ApplyWorkspaceRepository() try: return _ApplyCommand( diff --git a/packages/python-sdk/src/schemax/commands/bundle.py b/packages/python-sdk/src/schemax/commands/bundle.py index 122cfc3..b13689e 100644 --- a/packages/python-sdk/src/schemax/commands/bundle.py +++ b/packages/python-sdk/src/schemax/commands/bundle.py @@ -12,7 +12,7 @@ from pathlib import Path from typing import Any -from schemax.core.storage import read_project +from schemax.core.storage import get_target_config, read_project from schemax.version import SCHEMAX_VERSION _DEPLOY_SCRIPT_TEMPLATE = textwrap.dedent("""\ @@ -180,8 +180,8 @@ def generate_bundle( """ project = read_project(workspace) project_name: str = project.get("name", "schemax") - provider_config = project.get("provider", {}) - environments: dict[str, Any] = provider_config.get("environments", {}) + target_config = get_target_config(project) + environments: dict[str, Any] = target_config.get("environments", {}) if not environments: raise ValueError( diff --git a/packages/python-sdk/src/schemax/commands/diff.py b/packages/python-sdk/src/schemax/commands/diff.py index 9c2a769..badc4a3 100644 --- a/packages/python-sdk/src/schemax/commands/diff.py +++ b/packages/python-sdk/src/schemax/commands/diff.py @@ -14,6 +14,7 @@ from rich.table import Table from schemax.commands.sql import SQLGenerationError, build_catalog_mapping +from schemax.core.storage import _get_provider_from_target, get_target_config from schemax.core.workspace_repository import WorkspaceRepository from schemax.providers.base.operations import Operation from schemax.providers.base.provider import Provider @@ -32,7 +33,7 @@ def read_project(self, *, workspace: Path) -> dict[str, Any]: ... def read_snapshot(self, *, workspace: Path, version: str) -> dict[str, Any]: ... def get_environment_config( - self, *, project: dict[str, Any], environment: str + self, *, project: dict[str, Any], environment: str, scope: str | None = None ) -> dict[str, Any]: ... @@ -49,9 +50,11 @@ def read_snapshot(self, *, workspace: Path, version: str) -> dict[str, Any]: return self._repository.read_snapshot(workspace=workspace, version=version) def get_environment_config( - self, *, project: dict[str, Any], environment: str + self, *, project: dict[str, Any], environment: str, scope: str | None = None ) -> dict[str, Any]: - return self._repository.get_environment_config(project=project, environment=environment) + return self._repository.get_environment_config( + project=project, environment=environment, scope=scope + ) def generate_diff( @@ -89,7 +92,8 @@ def generate_diff( ) project = repository.read_project(workspace=workspace) - provider_id = project["provider"]["type"] + target_cfg = get_target_config(project) + provider_id = _get_provider_from_target(target_cfg) provider = ProviderRegistry.get(provider_id) if not provider: raise DiffError(f"Provider '{provider_id}' not found in registry") diff --git a/packages/python-sdk/src/schemax/commands/import_assets.py b/packages/python-sdk/src/schemax/commands/import_assets.py index a70d54c..beb9174 100644 --- a/packages/python-sdk/src/schemax/commands/import_assets.py +++ b/packages/python-sdk/src/schemax/commands/import_assets.py @@ -77,7 +77,7 @@ def create_snapshot( ) -> tuple[dict[str, Any], dict[str, Any]]: ... def get_environment_config( - self, *, project: dict[str, Any], environment: str + self, *, project: dict[str, Any], environment: str, scope: str | None = None ) -> dict[str, Any]: ... def write_project(self, *, workspace: Path, project: dict[str, Any]) -> None: ... @@ -122,9 +122,11 @@ def create_snapshot( ) def get_environment_config( - self, *, project: dict[str, Any], environment: str + self, *, project: dict[str, Any], environment: str, scope: str | None = None ) -> dict[str, Any]: - return self._repository.get_environment_config(project=project, environment=environment) + return self._repository.get_environment_config( + project=project, environment=environment, scope=scope + ) def write_project(self, *, workspace: Path, project: dict[str, Any]) -> None: self._repository.write_project(workspace=workspace, project=project) diff --git a/packages/python-sdk/src/schemax/commands/rollback.py b/packages/python-sdk/src/schemax/commands/rollback.py index 82b96d9..6c6cb7b 100644 --- a/packages/python-sdk/src/schemax/commands/rollback.py +++ b/packages/python-sdk/src/schemax/commands/rollback.py @@ -42,7 +42,7 @@ class _WorkspaceRepoPort(Protocol): def read_project(self, *, workspace: Path) -> dict[str, Any]: ... def get_environment_config( - self, *, project: dict[str, Any], environment: str + self, *, project: dict[str, Any], environment: str, scope: str | None = None ) -> dict[str, Any]: ... def load_current_state(self, *, workspace: Path, validate: bool = False) -> tuple[Any, ...]: ... @@ -62,9 +62,11 @@ def read_project(self, *, workspace: Path) -> dict[str, Any]: return self._repository.read_project(workspace=workspace) def get_environment_config( - self, *, project: dict[str, Any], environment: str + self, *, project: dict[str, Any], environment: str, scope: str | None = None ) -> dict[str, Any]: - return self._repository.get_environment_config(project=project, environment=environment) + return self._repository.get_environment_config( + project=project, environment=environment, scope=scope + ) def load_current_state(self, *, workspace: Path, validate: bool = False) -> tuple[Any, ...]: return self._repository.load_current_state(workspace=workspace, validate=validate) diff --git a/packages/python-sdk/src/schemax/commands/sql.py b/packages/python-sdk/src/schemax/commands/sql.py index 9db05f7..467e0cb 100644 --- a/packages/python-sdk/src/schemax/commands/sql.py +++ b/packages/python-sdk/src/schemax/commands/sql.py @@ -30,7 +30,7 @@ def read_snapshot(self, *, workspace: Path, version: str) -> dict[str, Any]: ... def load_current_state(self, *, workspace: Path, validate: bool = False) -> tuple[Any, ...]: ... def get_environment_config( - self, *, project: dict[str, Any], environment: str + self, *, project: dict[str, Any], environment: str, scope: str | None = None ) -> dict[str, Any]: ... @@ -50,9 +50,11 @@ def load_current_state(self, *, workspace: Path, validate: bool = False) -> tupl return self._repository.load_current_state(workspace=workspace, validate=validate) def get_environment_config( - self, *, project: dict[str, Any], environment: str + self, *, project: dict[str, Any], environment: str, scope: str | None = None ) -> dict[str, Any]: - return self._repository.get_environment_config(project=project, environment=environment) + return self._repository.get_environment_config( + project=project, environment=environment, scope=scope + ) # TODO: Snapshot-based SQL generation requires issue #56 to be implemented first @@ -128,6 +130,7 @@ def generate_sql_migration( _to_version: str | None = None, target_env: str | None = None, workspace_repo: _WorkspaceRepoPort | None = None, + scope: str | None = None, ) -> str: """Generate SQL migration script from schema changes @@ -140,6 +143,7 @@ def generate_sql_migration( snapshot: Optional snapshot version ('latest' or specific version like 'v0.1.0') target_env: Optional target environment (for catalog name mapping) workspace_repo: Optional repository override for tests/injection + scope: Optional v5 multi-target scope (reserved for future use) Returns: Generated SQL string @@ -147,6 +151,7 @@ def generate_sql_migration( Raises: SQLGenerationError: If SQL generation fails """ + del scope # v5 multi-target: plumbed from CLI, will be used when per-target state loads repository: _WorkspaceRepoPort = workspace_repo or _SqlWorkspaceRepository() try: return _generate_sql_impl( diff --git a/packages/python-sdk/src/schemax/commands/validate.py b/packages/python-sdk/src/schemax/commands/validate.py index 6b93a21..9ac8a25 100644 --- a/packages/python-sdk/src/schemax/commands/validate.py +++ b/packages/python-sdk/src/schemax/commands/validate.py @@ -221,6 +221,7 @@ def validate_project( workspace: Path, json_output: bool = False, workspace_repo: _WorkspaceRepoPort | None = None, + scope: str | None = None, ) -> bool: """Validate SchemaX project files. @@ -231,6 +232,7 @@ def validate_project( workspace: Path to SchemaX workspace json_output: If True, output results as JSON instead of rich console workspace_repo: Optional repository override for tests/injection + scope: Optional v5 multi-target scope (reserved for future use) Returns: True if validation passes @@ -239,6 +241,7 @@ def validate_project( ValidationError: If validation fails """ repository: _WorkspaceRepoPort = workspace_repo or _ValidateWorkspaceRepository() + del scope # v5 multi-target: plumbed from CLI, will be used when per-target state loads try: project, state, changelog, provider = _load_project_and_state(workspace, repository) except FileNotFoundError as e: diff --git a/packages/python-sdk/src/schemax/core/__init__.py b/packages/python-sdk/src/schemax/core/__init__.py index 828f681..f965c92 100644 --- a/packages/python-sdk/src/schemax/core/__init__.py +++ b/packages/python-sdk/src/schemax/core/__init__.py @@ -25,6 +25,7 @@ get_schemax_dir, get_snapshot_file_path, get_snapshots_dir, + get_target_config, get_uncommitted_ops_count, load_current_state, read_changelog, @@ -52,6 +53,7 @@ "ensure_schemax_dir", "get_changelog_file_path", "get_environment_config", + "get_target_config", "get_project_file_path", "get_schemax_dir", "get_snapshot_file_path", diff --git a/packages/python-sdk/src/schemax/core/storage.py b/packages/python-sdk/src/schemax/core/storage.py index 7757b1b..288a52e 100644 --- a/packages/python-sdk/src/schemax/core/storage.py +++ b/packages/python-sdk/src/schemax/core/storage.py @@ -1,8 +1,10 @@ """ -Storage Layer V4 - Multi-Environment Support +Storage Layer V5 - Multi-Target / Multi-Provider Support -Supports environment-specific catalog configurations with logical → physical name mapping. -Breaking change from v3: environments are now rich objects instead of simple arrays. +Supports multiple named targets (provider instances) per project, each managing +one scope of infrastructure. Transparent auto-migration from v4 → v5 on first load. + +V4 → V5 migration: wraps the single `provider` object into `targets: { "default": provider }`. """ import hashlib @@ -52,16 +54,18 @@ def _has_legacy_implicit_project_markers(project: dict[str, Any]) -> bool: settings = cast(dict[str, Any], project.get("settings", {})) if settings.get("catalogMode") == "single": return True - environments = ( - cast(dict[str, Any], project.get("provider", {})).get("environments", {}) - if isinstance(project.get("provider"), dict) - else {} - ) - if not isinstance(environments, dict): - return False - for env_config in environments.values(): - if not isinstance(env_config, dict): - continue + # Check v4 format (provider.environments) and v5 format (targets.*.environments) + all_environments: list[dict[str, Any]] = [] + if isinstance(project.get("provider"), dict): + envs = cast(dict[str, Any], project["provider"]).get("environments", {}) + if isinstance(envs, dict): + all_environments.extend(v for v in envs.values() if isinstance(v, dict)) + for target_cfg in (project.get("targets") or {}).values(): + if isinstance(target_cfg, dict): + envs = target_cfg.get("environments", {}) + if isinstance(envs, dict): + all_environments.extend(v for v in envs.values() if isinstance(v, dict)) + for env_config in all_environments: mappings = env_config.get("catalogMappings", {}) if isinstance(mappings, dict) and "__implicit__" in mappings: return True @@ -270,8 +274,51 @@ def ensure_schemax_dir(workspace_path: Path) -> None: snapshots_dir.mkdir(parents=True, exist_ok=True) +def _migrate_v4_to_v5(project: dict[str, Any]) -> dict[str, Any]: + """Migrate a v4 project dict to v5 in-memory, wrapping provider into targets.""" + old_provider = project.get("provider", {}) + if not isinstance(old_provider, dict): + old_provider = {} + project["version"] = 5 + project["targets"] = { + "default": old_provider, + } + project["defaultTarget"] = "default" + # Remove the old top-level provider key + project.pop("provider", None) + return project + + +def _get_provider_from_target(target_config: dict[str, Any]) -> str: + """Extract provider type string from a target config dict.""" + return str(target_config.get("type", "unity")) + + +def get_target_config(project: dict[str, Any], scope: str | None = None) -> dict[str, Any]: + """Get a specific target's configuration from a v5 project. + + Args: + project: Project data (v5) + scope: Target scope name. If None, uses defaultTarget. + + Returns: + Target configuration dict (contains type, version, environments, etc.) + + Raises: + ValueError: If target not found + """ + targets = project.get("targets", {}) + resolved_name = scope or project.get("defaultTarget", "default") + if resolved_name not in targets: + available = ", ".join(targets.keys()) + raise ValueError( + f"Target '{resolved_name}' not found in project. Available targets: {available}" + ) + return cast(dict[str, Any], targets[resolved_name]) + + def ensure_project_file(workspace_path: Path, provider_id: str = "unity") -> None: - """Initialize a new v4 project with environment configuration + """Initialize a new v5 project with environment configuration Args: workspace_path: Path to workspace @@ -282,11 +329,19 @@ def ensure_project_file(workspace_path: Path, provider_id: str = "unity") -> Non if project_path.exists(): with open(project_path, encoding="utf-8") as f: project = cast(dict[str, Any], json.load(f)) - if project.get("version") == 4: + version = project.get("version") + if version == 5: + return + if version == 4: + # Validate before migration (same check as read_project) + validate_workspace_not_legacy_implicit(project) + # Auto-migrate v4 → v5 + project = _migrate_v4_to_v5(project) + _write_json_atomic(project_path, project) return raise ValueError( - f"Project version {project.get('version')} not supported. " - "Please create a new project or manually migrate to v4." + f"Project version {version} not supported. " + "Please create a new project or manually migrate." ) workspace_name = workspace_path.name @@ -298,42 +353,45 @@ def ensure_project_file(workspace_path: Path, provider_id: str = "unity") -> Non ) new_project = { - "version": 4, + "version": 5, "name": workspace_name, - "provider": { - "type": provider_id, - "version": provider.info.version, - "environments": { - "dev": { - "topLevelName": f"dev_{workspace_name}", - "description": "Development environment", - "allowDrift": True, - "requireSnapshot": False, - "autoCreateTopLevel": True, - "autoCreateSchemaxSchema": True, - "catalogMappings": {}, - }, - "test": { - "topLevelName": f"test_{workspace_name}", - "description": "Test/staging environment", - "allowDrift": False, - "requireSnapshot": True, - "autoCreateTopLevel": True, - "autoCreateSchemaxSchema": True, - "catalogMappings": {}, - }, - "prod": { - "topLevelName": f"prod_{workspace_name}", - "description": "Production environment", - "allowDrift": False, - "requireSnapshot": True, - "requireApproval": False, - "autoCreateTopLevel": False, - "autoCreateSchemaxSchema": True, - "catalogMappings": {}, + "targets": { + "default": { + "type": provider_id, + "version": provider.info.version, + "environments": { + "dev": { + "topLevelName": f"dev_{workspace_name}", + "description": "Development environment", + "allowDrift": True, + "requireSnapshot": False, + "autoCreateTopLevel": True, + "autoCreateSchemaxSchema": True, + "catalogMappings": {}, + }, + "test": { + "topLevelName": f"test_{workspace_name}", + "description": "Test/staging environment", + "allowDrift": False, + "requireSnapshot": True, + "autoCreateTopLevel": True, + "autoCreateSchemaxSchema": True, + "catalogMappings": {}, + }, + "prod": { + "topLevelName": f"prod_{workspace_name}", + "description": "Production environment", + "allowDrift": False, + "requireSnapshot": True, + "requireApproval": False, + "autoCreateTopLevel": False, + "autoCreateSchemaxSchema": True, + "catalogMappings": {}, + }, }, }, }, + "defaultTarget": "default", "managedLocations": {}, "externalLocations": {}, **default_project_skeleton_tail(), @@ -350,22 +408,22 @@ def ensure_project_file(workspace_path: Path, provider_id: str = "unity") -> Non _write_json_atomic(get_changelog_file_path(workspace_path), new_changelog) provider_name = provider.info.name - print(f"[SchemaX] Initialized new v4 project: {workspace_name} with provider: {provider_name}") + print(f"[SchemaX] Initialized new v5 project: {workspace_name} with provider: {provider_name}") print("[SchemaX] Environments: dev, test, prod") def read_project(workspace_path: Path) -> dict[str, Any]: - """Read project file (v4 only) + """Read project file (v4 auto-migrated to v5, or v5 native) Args: workspace_path: Path to workspace Returns: - Project data + Project data (always v5 format) Raises: FileNotFoundError: If project file does not exist - ValueError: If project version is not v4 + ValueError: If project version is not v4 or v5 """ project_path = get_project_file_path(workspace_path) @@ -377,14 +435,20 @@ def read_project(workspace_path: Path) -> dict[str, Any]: with open(project_path, encoding="utf-8") as f: project = cast(dict[str, Any], json.load(f)) - # Enforce v4 - if project.get("version") != 4: + version = project.get("version") + if version == 4: + # Transparent v4 → v5 migration: upgrade in-memory and persist + validate_workspace_not_legacy_implicit(project) + project = _migrate_v4_to_v5(project) + _write_json_atomic(project_path, project) + elif version == 5: + pass # Already v5 + else: raise ValueError( - f"Project version {project.get('version')} not supported. " - "This version of SchemaX requires v4 projects. " - "Please create a new project or manually migrate to v4." + f"Project version {version} not supported. " + "This version of SchemaX requires v4 or v5 projects. " + "Please create a new project or manually migrate." ) - validate_workspace_not_legacy_implicit(project) return project @@ -460,13 +524,14 @@ def write_snapshot(workspace_path: Path, snapshot: dict[str, Any]) -> None: def load_current_state( - workspace_path: Path, validate: bool = False + workspace_path: Path, validate: bool = False, scope: str | None = None ) -> tuple[ProviderState, dict[str, Any], Provider, dict[str, Any] | None]: """Load current state using provider Args: workspace_path: Path to workspace validate: Whether to validate dependencies (default False for performance) + scope: Optional target scope (v5). Uses defaultTarget if None. Returns: Tuple of (state, changelog, provider, validation_result) @@ -477,7 +542,9 @@ def load_current_state( project = read_project(workspace_path) changelog = read_changelog(workspace_path) - return _load_current_state_from_data(workspace_path, project, changelog, validate=validate) + return _load_current_state_from_data( + workspace_path, project, changelog, validate=validate, scope=scope + ) def _load_current_state_from_data( @@ -486,14 +553,17 @@ def _load_current_state_from_data( changelog: dict[str, Any], *, validate: bool, + scope: str | None = None, ) -> tuple[ProviderState, dict[str, Any], Provider, dict[str, Any] | None]: """Load state using preloaded project/changelog data to avoid duplicate disk reads.""" validate_workspace_not_legacy_implicit(project, changelog) - provider = ProviderRegistry.get(project["provider"]["type"]) + # v5: resolve provider type from the target config + target_cfg = get_target_config(project, scope) + provider_type = _get_provider_from_target(target_cfg) + provider = ProviderRegistry.get(provider_type) if provider is None: raise ValueError( - f"Provider '{project['provider']['type']}' not found. " - "Please ensure the provider is installed." + f"Provider '{provider_type}' not found. Please ensure the provider is installed." ) if project["latestSnapshot"]: @@ -558,20 +628,23 @@ def validate_dependencies_internal( return {"errors": errors, "warnings": warnings} -def append_ops(workspace_path: Path, ops: list[Operation]) -> None: +def append_ops(workspace_path: Path, ops: list[Operation], scope: str | None = None) -> None: """Append operations to changelog Args: workspace_path: Path to workspace ops: Operations to append + scope: Optional target scope (v5). Uses defaultTarget if None. """ with WorkspaceSession(workspace_path) as session: project = session.read_project() changelog = session.read_changelog() - provider = ProviderRegistry.get(project["provider"]["type"]) + target_cfg = get_target_config(project, scope) + provider_type = _get_provider_from_target(target_cfg) + provider = ProviderRegistry.get(provider_type) if provider is None: - raise ValueError(f"Provider '{project['provider']['type']}' not found") + raise ValueError(f"Provider '{provider_type}' not found") for operation in ops: validation = provider.validate_operation(operation) @@ -774,10 +847,12 @@ def _get_next_version(current_version: str | None, settings: dict[str, Any]) -> return f"{version_prefix}{major}.{next_minor}.0" -def get_environment_config(project: dict[str, Any], environment: str) -> dict[str, Any]: +def get_environment_config( + project: dict[str, Any], environment: str, scope: str | None = None +) -> dict[str, Any]: """Get environment configuration from project. - Each entry under provider.environments. may include optional: + Each entry under targets..environments. may include optional: - importBaselineSnapshot (string, snapshot version) set when import --adopt-baseline runs; used to block rollback before baseline unless --force is used. @@ -790,8 +865,9 @@ def get_environment_config(project: dict[str, Any], environment: str) -> dict[st logical catalog "analytics". Args: - project: Project data (v4) + project: Project data (v5) environment: Environment name (e.g., "dev", "prod") + scope: Optional target scope. Uses defaultTarget if None. Returns: Environment configuration (reference into project dict) @@ -799,7 +875,8 @@ def get_environment_config(project: dict[str, Any], environment: str) -> dict[st Raises: ValueError: If environment not found """ - environments = project.get("provider", {}).get("environments", {}) + target_cfg = get_target_config(project, scope) + environments = target_cfg.get("environments", {}) if environment not in environments: available = ", ".join(environments.keys()) diff --git a/packages/python-sdk/src/schemax/core/workspace_repository.py b/packages/python-sdk/src/schemax/core/workspace_repository.py index 1659263..3771a28 100644 --- a/packages/python-sdk/src/schemax/core/workspace_repository.py +++ b/packages/python-sdk/src/schemax/core/workspace_repository.py @@ -15,6 +15,7 @@ ensure_project_file, get_environment_config, get_snapshot_file_path, + get_target_config, load_current_state, read_changelog, read_project, @@ -102,10 +103,16 @@ def create_snapshot( ) def get_environment_config( - self, *, project: dict[str, Any], environment: str + self, *, project: dict[str, Any], environment: str, scope: str | None = None ) -> dict[str, Any]: """Resolve one environment config from project configuration.""" - return get_environment_config(project, environment) + return get_environment_config(project, environment, scope=scope) + + def get_target_config( + self, *, project: dict[str, Any], scope: str | None = None + ) -> dict[str, Any]: + """Resolve one target config from project configuration.""" + return get_target_config(project, scope) def open_session(self, *, workspace: Path) -> WorkspaceSession: """Open a transactional workspace session.""" @@ -127,9 +134,11 @@ def snapshot_file_path(self, *, workspace: Path, version: str) -> Path: """Resolve one snapshot file path.""" return get_snapshot_file_path(workspace, version) - def load_current_state(self, *, workspace: Path, validate: bool = False) -> tuple[Any, ...]: + def load_current_state( + self, *, workspace: Path, validate: bool = False, scope: str | None = None + ) -> tuple[Any, ...]: """Load current provider state and changelog.""" - return load_current_state(workspace, validate=validate) + return load_current_state(workspace, validate=validate, scope=scope) @staticmethod def _normalize_operation(operation: Operation | dict[str, Any]) -> Operation: diff --git a/packages/python-sdk/src/schemax/providers/base/operations.py b/packages/python-sdk/src/schemax/providers/base/operations.py index 9b9461e..ab80a25 100644 --- a/packages/python-sdk/src/schemax/providers/base/operations.py +++ b/packages/python-sdk/src/schemax/providers/base/operations.py @@ -51,6 +51,7 @@ class Operation(BaseModel): op: str # Operation type with provider prefix target: str # ID of target object payload: dict[str, Any] # Operation-specific data + scope: str | None = None # v5 multi-target: which target scope this op belongs to class OperationMetadata(BaseModel): @@ -72,6 +73,7 @@ def create_operation( target: str, payload: dict[str, Any], op_id: str | None = None, + scope: str | None = None, ) -> Operation: """ Helper to create a new operation with defaults @@ -82,6 +84,7 @@ def create_operation( target: Target object ID payload: Operation payload op_id: Optional operation ID (generated if not provided) + scope: Optional target scope for v5 multi-target scoping Returns: Operation instance @@ -99,4 +102,5 @@ def create_operation( op=f"{provider}.{op_type}", target=target, payload=payload, + scope=scope, ) diff --git a/packages/python-sdk/src/schemax/version.py b/packages/python-sdk/src/schemax/version.py index b90471b..8aee3cb 100644 --- a/packages/python-sdk/src/schemax/version.py +++ b/packages/python-sdk/src/schemax/version.py @@ -1,3 +1,3 @@ """Single source of truth for SchemaX package/runtime version.""" -SCHEMAX_VERSION = "0.2.10" +SCHEMAX_VERSION = "0.2.11" diff --git a/packages/python-sdk/tests/conftest.py b/packages/python-sdk/tests/conftest.py index 4a39aa6..4c0e774 100644 --- a/packages/python-sdk/tests/conftest.py +++ b/packages/python-sdk/tests/conftest.py @@ -148,7 +148,7 @@ def empty_unity_state(): @pytest.fixture def initialized_workspace(temp_workspace): - """Workspace with initialized .schemax project (v4)""" + """Workspace with initialized .schemax project (v5)""" ensure_project_file(temp_workspace, provider_id="unity") return temp_workspace diff --git a/packages/python-sdk/tests/integration/live_helpers.py b/packages/python-sdk/tests/integration/live_helpers.py index 02ff631..8210e37 100644 --- a/packages/python-sdk/tests/integration/live_helpers.py +++ b/packages/python-sdk/tests/integration/live_helpers.py @@ -35,7 +35,8 @@ def write_project_env_overrides( """Override dev env topLevelName and catalogMappings in project.json.""" project_path = workspace / ".schemax" / "project.json" project = json.loads(project_path.read_text()) - dev_env = project["provider"]["environments"]["dev"] + scope = project.get("defaultTarget", "default") + dev_env = project["targets"][scope]["environments"]["dev"] dev_env["topLevelName"] = top_level_name dev_env["catalogMappings"] = catalog_mappings project_path.write_text(json.dumps(project, indent=2)) @@ -50,7 +51,8 @@ def write_project_managed_scope( """Set dev env managedCategories and/or existingObjects.catalog for scope filter tests.""" project_path = workspace / ".schemax" / "project.json" project = json.loads(project_path.read_text()) - dev_env = project["provider"]["environments"]["dev"] + scope = project.get("defaultTarget", "default") + dev_env = project["targets"][scope]["environments"]["dev"] if managed_categories is not None: dev_env["managedCategories"] = managed_categories if existing_catalogs is not None: @@ -71,7 +73,8 @@ def write_project_promote_envs( """ project_path = workspace / ".schemax" / "project.json" project = json.loads(project_path.read_text()) - envs = project["provider"]["environments"] + scope = project.get("defaultTarget", "default") + envs = project["targets"][scope]["environments"] tracking_dev = f"{resource_prefix}_promote_track_dev_{suffix}" physical_dev = f"{resource_prefix}_promote_dev_{suffix}" diff --git a/packages/python-sdk/tests/integration/test_bundle_integration.py b/packages/python-sdk/tests/integration/test_bundle_integration.py index cdd7224..f03de0f 100644 --- a/packages/python-sdk/tests/integration/test_bundle_integration.py +++ b/packages/python-sdk/tests/integration/test_bundle_integration.py @@ -30,7 +30,8 @@ def _write_project_with_envs( project = json.loads(project_path.read_text()) project["name"] = name if environments is not None: - project["provider"]["environments"] = environments + default_target = project.get("defaultTarget", "default") + project["targets"][default_target]["environments"] = environments project_path.write_text(json.dumps(project, indent=2)) diff --git a/packages/python-sdk/tests/integration/test_cli_command_paths.py b/packages/python-sdk/tests/integration/test_cli_command_paths.py index df80796..c0a7090 100644 --- a/packages/python-sdk/tests/integration/test_cli_command_paths.py +++ b/packages/python-sdk/tests/integration/test_cli_command_paths.py @@ -107,7 +107,8 @@ def test_sql_with_target(self, temp_workspace: Path) -> None: ], ) project = read_project(temp_workspace) - project["provider"]["environments"]["dev"]["catalogMappings"] = {"demo": "dev_demo"} + scope = project.get("defaultTarget", "default") + project["targets"][scope]["environments"]["dev"]["catalogMappings"] = {"demo": "dev_demo"} write_project(temp_workspace, project) result = invoke_cli("sql", "--target", "dev", str(temp_workspace)) @@ -181,7 +182,8 @@ def test_sql_with_target_and_snapshot(self, temp_workspace: Path) -> None: ], ) project = read_project(temp_workspace) - project["provider"]["environments"]["dev"]["catalogMappings"] = {"demo": "dev_demo"} + scope = project.get("defaultTarget", "default") + project["targets"][scope]["environments"]["dev"]["catalogMappings"] = {"demo": "dev_demo"} write_project(temp_workspace, project) invoke_cli("snapshot", "create", "--name", "S1", "--version", "v0.1.0", str(temp_workspace)) @@ -248,7 +250,8 @@ def test_diff_show_sql_and_details(self, temp_workspace: Path) -> None: def test_diff_with_target_env(self, temp_workspace: Path) -> None: self._setup_two_snapshots(temp_workspace) project = read_project(temp_workspace) - project["provider"]["environments"]["dev"]["catalogMappings"] = {"demo": "dev_demo"} + scope = project.get("defaultTarget", "default") + project["targets"][scope]["environments"]["dev"]["catalogMappings"] = {"demo": "dev_demo"} write_project(temp_workspace, project) result = invoke_cli( diff --git a/packages/python-sdk/tests/integration/test_cli_coverage_gaps.py b/packages/python-sdk/tests/integration/test_cli_coverage_gaps.py new file mode 100644 index 0000000..3c12e01 --- /dev/null +++ b/packages/python-sdk/tests/integration/test_cli_coverage_gaps.py @@ -0,0 +1,472 @@ +"""Integration tests for CLI command coverage gaps. + +Covers: validate (non-JSON, JSON envelope), sql (snapshot, output, target, JSON), +diff (--from/--to, show-sql, show-details), workspace-state, snapshot (create, tags, rebase), +bundle, runtime-info, changelog undo, apply error paths. +""" + +import json +from pathlib import Path + +import pytest + +from schemax.core.storage import ( + append_ops, + create_snapshot, + ensure_project_file, + read_project, + write_project, +) +from tests.utils import OperationBuilder +from tests.utils.cli_helpers import invoke_cli + +# ── Validate ───────────────────────────────────────────────────────── + + +@pytest.mark.integration +class TestValidateCoverage: + def test_json_envelope(self, temp_workspace: Path) -> None: + ensure_project_file(temp_workspace, provider_id="unity") + builder = OperationBuilder() + append_ops( + temp_workspace, + [ + builder.catalog.add_catalog("cat_1", "demo", op_id="op_1"), + ], + ) + result = invoke_cli("validate", "--json", str(temp_workspace)) + assert result.exit_code == 0 + data = json.loads(result.output) + # Output may be envelope or direct - check either format + assert data.get("valid") is True or data.get("status") == "success" + + def test_non_json_full_summary(self, temp_workspace: Path) -> None: + ensure_project_file(temp_workspace, provider_id="unity") + builder = OperationBuilder() + append_ops( + temp_workspace, + [ + builder.catalog.add_catalog("cat_1", "demo", op_id="op_1"), + builder.schema.add_schema("s1", "raw", "cat_1", op_id="op_2"), + builder.table.add_table("t1", "users", "s1", "delta", op_id="op_3"), + builder.column.add_column("c1", "t1", "id", "INT", op_id="op_4"), + builder.table.add_table("t2", "orders", "s1", "delta", op_id="op_5"), + builder.column.add_column("c2", "t2", "id", "INT", op_id="op_6"), + ], + ) + result = invoke_cli("validate", str(temp_workspace)) + assert result.exit_code == 0 + assert "Project:" in result.output + assert "Provider:" in result.output + assert "Tables:" in result.output + + def test_empty_project(self, temp_workspace: Path) -> None: + ensure_project_file(temp_workspace, provider_id="unity") + result = invoke_cli("validate", str(temp_workspace)) + assert result.exit_code == 0 + + def test_no_project_file(self, temp_workspace: Path) -> None: + result = invoke_cli("validate", str(temp_workspace)) + assert result.exit_code == 1 + + +# ── SQL ────────────────────────────────────────────────────────────── + + +@pytest.mark.integration +class TestSqlCoverage: + def test_from_snapshot(self, temp_workspace: Path) -> None: + ensure_project_file(temp_workspace, provider_id="unity") + builder = OperationBuilder() + append_ops( + temp_workspace, + [ + builder.catalog.add_catalog("cat_1", "demo", op_id="op_1"), + builder.schema.add_schema("s1", "raw", "cat_1", op_id="op_2"), + ], + ) + invoke_cli( + "snapshot", + "create", + "--name", + "S1", + "--version", + "v0.1.0", + str(temp_workspace), + ) + result = invoke_cli("sql", "--snapshot", "v0.1.0", str(temp_workspace)) + assert result.exit_code == 0 + + def test_output_file(self, temp_workspace: Path) -> None: + ensure_project_file(temp_workspace, provider_id="unity") + builder = OperationBuilder() + append_ops( + temp_workspace, + [ + builder.catalog.add_catalog("cat_1", "demo", op_id="op_1"), + builder.schema.add_schema("s1", "raw", "cat_1", op_id="op_2"), + builder.table.add_table("t1", "users", "s1", "delta", op_id="op_3"), + builder.column.add_column("c1", "t1", "id", "INT", op_id="op_4"), + ], + ) + out = temp_workspace / "output.sql" + result = invoke_cli("sql", "--output", str(out), str(temp_workspace)) + assert result.exit_code == 0 + assert out.exists() + assert "CREATE" in out.read_text() + + def test_target_env(self, temp_workspace: Path) -> None: + ensure_project_file(temp_workspace, provider_id="unity") + builder = OperationBuilder() + append_ops( + temp_workspace, + [ + builder.catalog.add_catalog("cat_1", "demo", op_id="op_1"), + builder.schema.add_schema("s1", "raw", "cat_1", op_id="op_2"), + ], + ) + project = read_project(temp_workspace) + scope = project.get("defaultTarget", "default") + project["targets"][scope]["environments"]["dev"]["catalogMappings"] = {"demo": "dev_demo"} + write_project(temp_workspace, project) + result = invoke_cli("sql", "--target", "dev", str(temp_workspace)) + assert result.exit_code == 0 + + def test_empty_project(self, temp_workspace: Path) -> None: + ensure_project_file(temp_workspace, provider_id="unity") + result = invoke_cli("sql", str(temp_workspace)) + assert result.exit_code == 0 + + def test_json_envelope(self, temp_workspace: Path) -> None: + ensure_project_file(temp_workspace, provider_id="unity") + builder = OperationBuilder() + append_ops( + temp_workspace, + [ + builder.catalog.add_catalog("cat_1", "demo", op_id="op_1"), + ], + ) + result = invoke_cli("sql", "--json", str(temp_workspace)) + assert result.exit_code == 0 + data = json.loads(result.output) + assert data.get("status") == "success" or "sql" in str(data) + + def test_snapshot_latest(self, temp_workspace: Path) -> None: + ensure_project_file(temp_workspace, provider_id="unity") + builder = OperationBuilder() + append_ops( + temp_workspace, + [ + builder.catalog.add_catalog("cat_1", "demo", op_id="op_1"), + ], + ) + invoke_cli( + "snapshot", + "create", + "--name", + "S1", + "--version", + "v0.1.0", + str(temp_workspace), + ) + result = invoke_cli("sql", "--snapshot", "latest", str(temp_workspace)) + assert result.exit_code == 0 + + +# ── Diff ───────────────────────────────────────────────────────────── + + +@pytest.mark.integration +class TestDiffCoverage: + def _setup_two_snapshots(self, workspace: Path) -> None: + ensure_project_file(workspace, provider_id="unity") + builder = OperationBuilder() + append_ops( + workspace, + [ + builder.catalog.add_catalog("cat_1", "demo", op_id="op_1"), + builder.schema.add_schema("s1", "raw", "cat_1", op_id="op_2"), + ], + ) + invoke_cli( + "snapshot", + "create", + "--name", + "S1", + "--version", + "v0.1.0", + str(workspace), + ) + append_ops( + workspace, + [ + builder.table.add_table("t1", "users", "s1", "delta", op_id="op_3"), + builder.column.add_column("c1", "t1", "id", "INT", op_id="op_4"), + ], + ) + invoke_cli( + "snapshot", + "create", + "--name", + "S2", + "--version", + "v0.2.0", + str(workspace), + ) + + def test_basic_diff(self, temp_workspace: Path) -> None: + self._setup_two_snapshots(temp_workspace) + result = invoke_cli("diff", "--from", "v0.1.0", "--to", "v0.2.0", str(temp_workspace)) + assert result.exit_code == 0 + + def test_show_sql(self, temp_workspace: Path) -> None: + self._setup_two_snapshots(temp_workspace) + result = invoke_cli( + "diff", + "--from", + "v0.1.0", + "--to", + "v0.2.0", + "--show-sql", + str(temp_workspace), + ) + assert result.exit_code == 0 + + def test_show_details(self, temp_workspace: Path) -> None: + self._setup_two_snapshots(temp_workspace) + result = invoke_cli( + "diff", + "--from", + "v0.1.0", + "--to", + "v0.2.0", + "--show-details", + str(temp_workspace), + ) + assert result.exit_code == 0 + + +# ── Workspace State ────────────────────────────────────────────────── + + +@pytest.mark.integration +class TestWorkspaceStateCoverage: + def test_json(self, temp_workspace: Path) -> None: + ensure_project_file(temp_workspace, provider_id="unity") + builder = OperationBuilder() + append_ops( + temp_workspace, + [ + builder.catalog.add_catalog("cat_1", "demo", op_id="op_1"), + builder.schema.add_schema("s1", "raw", "cat_1", op_id="op_2"), + ], + ) + result = invoke_cli("workspace-state", "--json", str(temp_workspace)) + assert result.exit_code == 0 + data = json.loads(result.output) + assert "catalogs" in str(data).lower() + + def test_non_json(self, temp_workspace: Path) -> None: + ensure_project_file(temp_workspace, provider_id="unity") + builder = OperationBuilder() + append_ops( + temp_workspace, + [ + builder.catalog.add_catalog("cat_1", "demo", op_id="op_1"), + builder.schema.add_schema("s1", "raw", "cat_1", op_id="op_2"), + builder.table.add_table("t1", "users", "s1", "delta", op_id="op_3"), + ], + ) + result = invoke_cli("workspace-state", str(temp_workspace)) + assert result.exit_code == 0 + + +# ── Snapshot ───────────────────────────────────────────────────────── + + +@pytest.mark.integration +class TestSnapshotCoverage: + def test_create_via_cli(self, temp_workspace: Path) -> None: + ensure_project_file(temp_workspace, provider_id="unity") + builder = OperationBuilder() + append_ops( + temp_workspace, + [ + builder.catalog.add_catalog("cat_1", "demo", op_id="op_1"), + builder.schema.add_schema("s1", "raw", "cat_1", op_id="op_2"), + ], + ) + result = invoke_cli( + "snapshot", + "create", + "--name", + "Test Snapshot", + "--version", + "v0.1.0", + str(temp_workspace), + ) + assert result.exit_code == 0 + + def test_create_with_tags(self, temp_workspace: Path) -> None: + ensure_project_file(temp_workspace, provider_id="unity") + builder = OperationBuilder() + append_ops( + temp_workspace, + [ + builder.catalog.add_catalog("cat_1", "demo", op_id="op_1"), + ], + ) + result = invoke_cli( + "snapshot", + "create", + "--name", + "Tagged", + "--version", + "v0.1.0", + "--tags", + "release,stable", + str(temp_workspace), + ) + assert result.exit_code == 0 + + def test_rebase_no_previous(self, temp_workspace: Path) -> None: + ensure_project_file(temp_workspace, provider_id="unity") + builder = OperationBuilder() + append_ops( + temp_workspace, + [ + builder.catalog.add_catalog("cat_1", "demo", op_id="op_1"), + ], + ) + create_snapshot(temp_workspace, name="Base", version="v0.1.0") + result = invoke_cli("snapshot", "rebase", "v0.1.0", str(temp_workspace)) + assert result.exit_code == 1 + assert "previousSnapshot" in result.output or "no previous" in result.output.lower() + + def test_validate_snapshots(self, temp_workspace: Path) -> None: + ensure_project_file(temp_workspace, provider_id="unity") + builder = OperationBuilder() + append_ops( + temp_workspace, + [ + builder.catalog.add_catalog("cat_1", "demo", op_id="op_1"), + ], + ) + create_snapshot(temp_workspace, name="Base", version="v0.1.0") + result = invoke_cli("snapshot", "validate", str(temp_workspace)) + assert result.exit_code in (0, 1) + + +# ── Bundle ─────────────────────────────────────────────────────────── + + +@pytest.mark.integration +class TestBundleCoverage: + def test_basic(self, temp_workspace: Path) -> None: + ensure_project_file(temp_workspace, provider_id="unity") + builder = OperationBuilder() + append_ops( + temp_workspace, + [ + builder.catalog.add_catalog("cat_1", "demo", op_id="op_1"), + builder.schema.add_schema("s1", "raw", "cat_1", op_id="op_2"), + ], + ) + # Bundle works from cwd, so use cwd parameter + result = invoke_cli("bundle", cwd=temp_workspace) + assert result.exit_code == 0 + + +# ── Runtime Info ───────────────────────────────────────────────────── + + +@pytest.mark.integration +class TestRuntimeInfoCoverage: + def test_json(self) -> None: + result = invoke_cli("runtime-info", "--json") + assert result.exit_code == 0 + data = json.loads(result.output) + assert data.get("status") == "success" + assert "cliVersion" in str(data) + + def test_non_json(self) -> None: + result = invoke_cli("runtime-info") + assert result.exit_code == 0 + + +# ── Changelog Undo ─────────────────────────────────────────────────── + + +@pytest.mark.integration +class TestChangelogUndoCoverage: + def test_undo_specific_op(self, temp_workspace: Path) -> None: + ensure_project_file(temp_workspace, provider_id="unity") + builder = OperationBuilder() + append_ops( + temp_workspace, + [ + builder.catalog.add_catalog("cat_1", "demo", op_id="op_1"), + builder.schema.add_schema("s1", "raw", "cat_1", op_id="op_2"), + ], + ) + result = invoke_cli("changelog", "undo", "--op-id", "op_2", str(temp_workspace)) + assert result.exit_code == 0 + + def test_undo_no_op_ids(self, temp_workspace: Path) -> None: + """Undo with no op-ids should still work (undo last).""" + ensure_project_file(temp_workspace, provider_id="unity") + builder = OperationBuilder() + append_ops( + temp_workspace, + [ + builder.catalog.add_catalog("cat_1", "demo", op_id="op_1"), + ], + ) + result = invoke_cli("changelog", "undo", str(temp_workspace)) + # May succeed (undo last) or fail - depends on implementation + assert result.exit_code in (0, 1) + + +# ── Apply Error Paths ──────────────────────────────────────────────── + + +@pytest.mark.integration +class TestApplyErrorCoverage: + def test_no_credentials_json(self, temp_workspace: Path) -> None: + ensure_project_file(temp_workspace, provider_id="unity") + builder = OperationBuilder() + append_ops( + temp_workspace, + [ + builder.catalog.add_catalog("cat_1", "demo", op_id="op_1"), + builder.schema.add_schema("s1", "raw", "cat_1", op_id="op_2"), + ], + ) + result = invoke_cli( + "apply", + "--target", + "dev", + "--profile", + "nonexistent-profile", + "--warehouse-id", + "dummy-wh", + "--no-interaction", + "--json", + str(temp_workspace), + ) + assert result.exit_code == 1 + + def test_no_ops(self, temp_workspace: Path) -> None: + ensure_project_file(temp_workspace, provider_id="unity") + result = invoke_cli( + "apply", + "--target", + "dev", + "--profile", + "nonexistent-profile", + "--warehouse-id", + "dummy-wh", + "--no-interaction", + str(temp_workspace), + ) + assert result.exit_code in (0, 1) diff --git a/packages/python-sdk/tests/integration/test_command_error_paths.py b/packages/python-sdk/tests/integration/test_command_error_paths.py index 0f3a17b..f3e7a5f 100644 --- a/packages/python-sdk/tests/integration/test_command_error_paths.py +++ b/packages/python-sdk/tests/integration/test_command_error_paths.py @@ -25,7 +25,8 @@ def test_apply_json_fails_with_auth_error_non_live(temp_workspace: Path) -> None ], ) project = read_project(temp_workspace) - project["provider"]["environments"]["dev"]["catalogMappings"] = {"bronze": "dev_bronze"} + scope = project.get("defaultTarget", "default") + project["targets"][scope]["environments"]["dev"]["catalogMappings"] = {"bronze": "dev_bronze"} write_project(temp_workspace, project) create_result = invoke_cli( "snapshot", diff --git a/packages/python-sdk/tests/integration/test_diff_error_paths.py b/packages/python-sdk/tests/integration/test_diff_error_paths.py new file mode 100644 index 0000000..9160d61 --- /dev/null +++ b/packages/python-sdk/tests/integration/test_diff_error_paths.py @@ -0,0 +1,139 @@ +"""Integration tests for diff command error paths. + +Covers: +- Same version comparison +- Missing snapshot files +- Invalid snapshot structure +- No changes between versions +- Diff with show-sql and target environment +""" + +from pathlib import Path + +import pytest + +from schemax.core.storage import ( + append_ops, + create_snapshot, + ensure_project_file, + read_project, + write_project, +) +from tests.utils import OperationBuilder +from tests.utils.cli_helpers import invoke_cli + + +@pytest.mark.integration +class TestDiffErrors: + """Test diff command error paths.""" + + def test_same_version(self, temp_workspace: Path) -> None: + """Diff with same --from and --to version fails.""" + ensure_project_file(temp_workspace, provider_id="unity") + builder = OperationBuilder() + append_ops( + temp_workspace, + [builder.catalog.add_catalog("cat_1", "demo", op_id="op_1")], + ) + create_snapshot(temp_workspace, name="S1", version="v0.1.0") + + result = invoke_cli("diff", "--from", "v0.1.0", "--to", "v0.1.0", str(temp_workspace)) + assert result.exit_code == 1 + assert "same version" in result.output.lower() or "Cannot diff" in result.output + + def test_missing_from_snapshot(self, temp_workspace: Path) -> None: + """Diff with non-existent --from snapshot.""" + ensure_project_file(temp_workspace, provider_id="unity") + builder = OperationBuilder() + append_ops( + temp_workspace, + [builder.catalog.add_catalog("cat_1", "demo", op_id="op_1")], + ) + create_snapshot(temp_workspace, name="S1", version="v0.1.0") + + result = invoke_cli("diff", "--from", "v99.0.0", "--to", "v0.1.0", str(temp_workspace)) + assert result.exit_code == 1 + assert "not found" in result.output.lower() or "error" in result.output.lower() + + def test_missing_to_snapshot(self, temp_workspace: Path) -> None: + """Diff with non-existent --to snapshot.""" + ensure_project_file(temp_workspace, provider_id="unity") + builder = OperationBuilder() + append_ops( + temp_workspace, + [builder.catalog.add_catalog("cat_1", "demo", op_id="op_1")], + ) + create_snapshot(temp_workspace, name="S1", version="v0.1.0") + + result = invoke_cli("diff", "--from", "v0.1.0", "--to", "v99.0.0", str(temp_workspace)) + assert result.exit_code == 1 + + +@pytest.mark.integration +class TestDiffNoChanges: + """Test diff when no changes exist between versions.""" + + def test_identical_snapshots(self, temp_workspace: Path) -> None: + """Two snapshots with same ops produce no diff.""" + ensure_project_file(temp_workspace, provider_id="unity") + builder = OperationBuilder() + append_ops( + temp_workspace, + [ + builder.catalog.add_catalog("cat_1", "demo", op_id="op_1"), + builder.schema.add_schema("s1", "raw", "cat_1", op_id="op_2"), + ], + ) + create_snapshot(temp_workspace, name="S1", version="v0.1.0") + + # Create second snapshot without adding new ops (same state) + create_snapshot(temp_workspace, name="S2", version="v0.2.0") + + result = invoke_cli("diff", "--from", "v0.1.0", "--to", "v0.2.0", str(temp_workspace)) + assert result.exit_code == 0 + assert "No changes" in result.output or "0" in result.output + + +@pytest.mark.integration +class TestDiffWithTargetEnv: + """Test diff with --target for catalog mapping.""" + + def test_diff_show_sql_with_target(self, temp_workspace: Path) -> None: + """Diff with --show-sql and target environment applies catalog mapping.""" + ensure_project_file(temp_workspace, provider_id="unity") + builder = OperationBuilder() + append_ops( + temp_workspace, + [ + builder.catalog.add_catalog("cat_1", "analytics", op_id="op_1"), + builder.schema.add_schema("s1", "raw", "cat_1", op_id="op_2"), + ], + ) + create_snapshot(temp_workspace, name="S1", version="v0.1.0") + + append_ops( + temp_workspace, + [ + builder.table.add_table("t1", "users", "s1", "delta", op_id="op_3"), + builder.column.add_column("c1", "t1", "id", "INT", op_id="op_4"), + ], + ) + create_snapshot(temp_workspace, name="S2", version="v0.2.0") + + project = read_project(temp_workspace) + scope = project.get("defaultTarget", "default") + project["targets"][scope]["environments"]["dev"]["catalogMappings"] = { + "analytics": "dev_analytics" + } + write_project(temp_workspace, project) + + result = invoke_cli( + "diff", + "--from", + "v0.1.0", + "--to", + "v0.2.0", + "--show-sql", + str(temp_workspace), + ) + assert result.exit_code == 0 diff --git a/packages/python-sdk/tests/integration/test_live_e2e_command_matrix.py b/packages/python-sdk/tests/integration/test_live_e2e_command_matrix.py index 822a736..13e131d 100644 --- a/packages/python-sdk/tests/integration/test_live_e2e_command_matrix.py +++ b/packages/python-sdk/tests/integration/test_live_e2e_command_matrix.py @@ -146,7 +146,8 @@ def test_live_command_matrix(tmp_path: Path) -> None: # Keep environment mappings complete for all logical catalogs in state. project_path = workspace / ".schemax" / "project.json" project = json.loads(project_path.read_text()) - dev_env = project["provider"]["environments"]["dev"] + scope = project.get("defaultTarget", "default") + dev_env = project["targets"][scope]["environments"]["dev"] mappings = dict(dev_env.get("catalogMappings") or {}) mappings[f"local_live_{suffix}"] = f"local_live_{suffix}" dev_env["catalogMappings"] = mappings diff --git a/packages/python-sdk/tests/integration/test_sql_error_paths.py b/packages/python-sdk/tests/integration/test_sql_error_paths.py new file mode 100644 index 0000000..1228ab7 --- /dev/null +++ b/packages/python-sdk/tests/integration/test_sql_error_paths.py @@ -0,0 +1,207 @@ +"""Integration tests for SQL generation command error paths. + +Covers: +- build_catalog_mapping: non-dict catalogMappings, missing catalog mappings +- Snapshot SQL: latest with no snapshots, snapshot without operations +- Empty changelog +- External table logging +- Managed/external location logging +""" + +from pathlib import Path + +import pytest + +from schemax.core.storage import ( + append_ops, + create_snapshot, + ensure_project_file, + read_project, + write_project, +) +from tests.utils import OperationBuilder +from tests.utils.cli_helpers import invoke_cli + + +@pytest.mark.integration +class TestSqlCatalogMappingErrors: + """Test build_catalog_mapping validation.""" + + def test_missing_catalog_mapping_error(self, temp_workspace: Path) -> None: + """Error when environment has no catalogMappings for logical catalogs.""" + ensure_project_file(temp_workspace, provider_id="unity") + builder = OperationBuilder() + append_ops( + temp_workspace, + [ + builder.catalog.add_catalog("cat_1", "analytics", op_id="op_1"), + builder.schema.add_schema("s1", "raw", "cat_1", op_id="op_2"), + ], + ) + project = read_project(temp_workspace) + scope = project.get("defaultTarget", "default") + # Set up environment without catalogMappings + project["targets"][scope]["environments"]["dev"]["catalogMappings"] = {} + write_project(temp_workspace, project) + + result = invoke_cli("sql", "--target", "dev", str(temp_workspace)) + assert result.exit_code == 1 + assert "Missing catalog mapping" in result.output or "catalogMapping" in result.output + + def test_valid_catalog_mapping(self, temp_workspace: Path) -> None: + """SQL generation works with proper catalog mappings.""" + ensure_project_file(temp_workspace, provider_id="unity") + builder = OperationBuilder() + append_ops( + temp_workspace, + [ + builder.catalog.add_catalog("cat_1", "analytics", op_id="op_1"), + builder.schema.add_schema("s1", "raw", "cat_1", op_id="op_2"), + builder.table.add_table("t1", "users", "s1", "delta", op_id="op_3"), + builder.column.add_column("c1", "t1", "id", "INT", op_id="op_4"), + ], + ) + project = read_project(temp_workspace) + scope = project.get("defaultTarget", "default") + project["targets"][scope]["environments"]["dev"]["catalogMappings"] = { + "analytics": "dev_analytics" + } + write_project(temp_workspace, project) + + result = invoke_cli("sql", "--target", "dev", str(temp_workspace)) + assert result.exit_code == 0 + assert "dev_analytics" in result.output + + +@pytest.mark.integration +class TestSqlSnapshotErrors: + """Test snapshot-based SQL error paths.""" + + def test_latest_no_snapshots(self, temp_workspace: Path) -> None: + """Using --snapshot latest with no snapshots fails.""" + ensure_project_file(temp_workspace, provider_id="unity") + builder = OperationBuilder() + append_ops( + temp_workspace, + [builder.catalog.add_catalog("cat_1", "demo", op_id="op_1")], + ) + result = invoke_cli("sql", "--snapshot", "latest", str(temp_workspace)) + assert result.exit_code == 1 + + def test_nonexistent_snapshot_version(self, temp_workspace: Path) -> None: + """Using --snapshot with a version that doesn't exist.""" + ensure_project_file(temp_workspace, provider_id="unity") + builder = OperationBuilder() + append_ops( + temp_workspace, + [builder.catalog.add_catalog("cat_1", "demo", op_id="op_1")], + ) + result = invoke_cli("sql", "--snapshot", "v99.0.0", str(temp_workspace)) + assert result.exit_code == 1 + + def test_snapshot_without_operations(self, temp_workspace: Path) -> None: + """Snapshot that doesn't preserve operations raises error.""" + ensure_project_file(temp_workspace, provider_id="unity") + builder = OperationBuilder() + append_ops( + temp_workspace, + [builder.catalog.add_catalog("cat_1", "demo", op_id="op_1")], + ) + # Create snapshot (doesn't preserve operations by default without the flag) + create_snapshot(temp_workspace, name="S1", version="v0.1.0") + + result = invoke_cli("sql", "--snapshot", "v0.1.0", str(temp_workspace)) + # Should fail because snapshot doesn't have operations + # Or succeed if operations are preserved - either way it handles it + assert result.exit_code in (0, 1) + + +@pytest.mark.integration +class TestSqlEmptyAndEdgeCases: + """Test edge cases for SQL generation.""" + + def test_empty_changelog_no_ops(self, temp_workspace: Path) -> None: + """SQL with no operations prints warning.""" + ensure_project_file(temp_workspace, provider_id="unity") + result = invoke_cli("sql", str(temp_workspace)) + assert result.exit_code == 0 + assert "No operations" in result.output or result.output.strip() == "" + + def test_sql_with_external_tables(self, temp_workspace: Path) -> None: + """SQL generation with external table ops logs external details.""" + ensure_project_file(temp_workspace, provider_id="unity") + builder = OperationBuilder() + append_ops( + temp_workspace, + [ + builder.catalog.add_catalog("cat_1", "analytics", op_id="op_1"), + builder.schema.add_schema("s1", "raw", "cat_1", op_id="op_2"), + builder.table.add_table( + "t1", + "events", + "s1", + "delta", + options={"external": True, "external_location_name": "landing"}, + op_id="op_3", + ), + builder.column.add_column("c1", "t1", "id", "INT", op_id="op_4"), + ], + ) + project = read_project(temp_workspace) + scope = project.get("defaultTarget", "default") + project["targets"][scope]["environments"]["dev"]["catalogMappings"] = { + "analytics": "dev_analytics" + } + project["externalLocations"] = { + "landing": {"paths": {"dev": "abfss://landing@storage.dfs.core.windows.net/data"}} + } + write_project(temp_workspace, project) + + result = invoke_cli("sql", "--target", "dev", str(temp_workspace)) + assert result.exit_code == 0 + + def test_sql_with_managed_locations(self, temp_workspace: Path) -> None: + """SQL generation with managed locations logs location details.""" + ensure_project_file(temp_workspace, provider_id="unity") + builder = OperationBuilder() + append_ops( + temp_workspace, + [ + builder.catalog.add_catalog( + "cat_1", "warehouse", managed_location_name="ml1", op_id="op_1" + ), + builder.schema.add_schema("s1", "raw", "cat_1", op_id="op_2"), + ], + ) + project = read_project(temp_workspace) + scope = project.get("defaultTarget", "default") + project["targets"][scope]["environments"]["dev"]["catalogMappings"] = { + "warehouse": "dev_warehouse" + } + project["managedLocations"] = { + "ml1": {"paths": {"dev": "abfss://container@account.dfs.core.windows.net/ml1"}} + } + write_project(temp_workspace, project) + + result = invoke_cli("sql", "--target", "dev", str(temp_workspace)) + assert result.exit_code == 0 + assert "MANAGED LOCATION" in result.output or "CREATE CATALOG" in result.output + + def test_sql_json_with_target(self, temp_workspace: Path) -> None: + """SQL JSON output with target environment.""" + ensure_project_file(temp_workspace, provider_id="unity") + builder = OperationBuilder() + append_ops( + temp_workspace, + [ + builder.catalog.add_catalog("cat_1", "demo", op_id="op_1"), + builder.schema.add_schema("s1", "raw", "cat_1", op_id="op_2"), + ], + ) + project = read_project(temp_workspace) + scope = project.get("defaultTarget", "default") + project["targets"][scope]["environments"]["dev"]["catalogMappings"] = {"demo": "dev_demo"} + write_project(temp_workspace, project) + + result = invoke_cli("sql", "--json", "--target", "dev", str(temp_workspace)) + assert result.exit_code == 0 diff --git a/packages/python-sdk/tests/integration/test_sql_gen_batched.py b/packages/python-sdk/tests/integration/test_sql_gen_batched.py new file mode 100644 index 0000000..5971e15 --- /dev/null +++ b/packages/python-sdk/tests/integration/test_sql_gen_batched.py @@ -0,0 +1,277 @@ +"""Integration tests for SQL generator batching paths. + +Covers: +- _generate_batched_create_sql for catalogs, schemas, tables (lines 476-536) +- _generate_batched_alter_sql for tables (lines 538-599) +- Operation categorization: column, property, constraint, governance, reorder, tags +""" + +import pytest + +from schemax.providers.unity.models import UnityState +from schemax.providers.unity.sql_generator import UnitySQLGenerator +from schemax.providers.unity.state_reducer import apply_operations +from tests.utils import OperationBuilder, ops_catalog_schema_table + + +def _empty(): + return UnityState(catalogs=[]) + + +def _gen_with_mapping(ops: list, **gen_kwargs): + state = apply_operations(_empty(), ops) + gen = UnitySQLGenerator(state, **gen_kwargs) + return gen.generate_sql_with_mapping(ops) + + +def _gen_sql(ops: list, **gen_kwargs) -> str: + state = apply_operations(_empty(), ops) + gen = UnitySQLGenerator(state, **gen_kwargs) + return gen.generate_sql(ops) + + +# ── Batched CREATE: Catalogs ───────────────────────────────────────── + + +@pytest.mark.integration +class TestBatchedCreateCatalog: + def test_create_catalog_with_update_squashed(self) -> None: + builder = OperationBuilder() + ops = [ + builder.catalog.add_catalog("cat_1", "analytics", op_id="op_1"), + builder.catalog.update_catalog("cat_1", comment="Analytics hub", op_id="op_2"), + ] + result = _gen_with_mapping(ops) + assert "CREATE CATALOG" in result.sql + assert "`analytics`" in result.sql + + def test_create_catalog_with_managed_location(self) -> None: + builder = OperationBuilder() + ops = [ + builder.catalog.add_catalog( + "cat_1", "warehouse", managed_location_name="ml1", op_id="op_1" + ), + ] + result = _gen_with_mapping( + ops, + managed_locations={ + "ml1": {"paths": {"dev": "abfss://container@account.dfs.core.windows.net/ml1"}} + }, + environment_name="dev", + ) + assert "CREATE CATALOG" in result.sql + assert "MANAGED LOCATION" in result.sql + assert "abfss://" in result.sql + + +# ── Batched CREATE: Schemas ────────────────────────────────────────── + + +@pytest.mark.integration +class TestBatchedCreateSchema: + def test_create_schema_with_update_squashed(self) -> None: + builder = OperationBuilder() + ops = [ + builder.catalog.add_catalog("cat_1", "analytics", op_id="op_1"), + builder.schema.add_schema("s1", "raw", "cat_1", op_id="op_2"), + builder.schema.update_schema("s1", comment="Raw data", op_id="op_3"), + ] + result = _gen_with_mapping(ops) + assert "CREATE SCHEMA" in result.sql + assert "`raw`" in result.sql + + def test_create_schema_with_managed_location(self) -> None: + builder = OperationBuilder() + ops = [ + builder.catalog.add_catalog("cat_1", "analytics", op_id="op_1"), + builder.schema.add_schema("s1", "raw", "cat_1", op_id="op_2"), + builder.schema.update_schema("s1", managed_location_name="ml1", op_id="op_3"), + ] + result = _gen_with_mapping( + ops, + managed_locations={ + "ml1": {"paths": {"dev": "abfss://container@account.dfs.core.windows.net/raw"}} + }, + environment_name="dev", + ) + assert "CREATE SCHEMA" in result.sql + assert "MANAGED LOCATION" in result.sql + + +# ── Batched CREATE: Tables ─────────────────────────────────────────── + + +@pytest.mark.integration +class TestBatchedCreateTable: + def test_create_table_with_columns(self) -> None: + builder = OperationBuilder() + ops = [ + builder.catalog.add_catalog("cat_1", "analytics", op_id="op_1"), + builder.schema.add_schema("s1", "raw", "cat_1", op_id="op_2"), + builder.table.add_table("t1", "events", "s1", "delta", op_id="op_3"), + builder.column.add_column("c1", "t1", "id", "BIGINT", nullable=False, op_id="op_4"), + builder.column.add_column("c2", "t1", "name", "STRING", op_id="op_5"), + builder.column.add_column("c3", "t1", "ts", "TIMESTAMP", op_id="op_6"), + ] + result = _gen_with_mapping(ops) + assert "CREATE TABLE" in result.sql + assert "`events`" in result.sql + assert "`id`" in result.sql + + def test_create_table_with_properties(self) -> None: + builder = OperationBuilder() + ops = [ + builder.catalog.add_catalog("cat_1", "analytics", op_id="op_1"), + builder.schema.add_schema("s1", "raw", "cat_1", op_id="op_2"), + builder.table.add_table("t1", "events", "s1", "delta", op_id="op_3"), + builder.column.add_column("c1", "t1", "id", "INT", op_id="op_4"), + builder.table.set_table_property( + "t1", "delta.autoOptimize.optimizeWrite", "true", op_id="op_5" + ), + ] + result = _gen_with_mapping(ops) + assert "CREATE TABLE" in result.sql + assert "delta.autoOptimize" in result.sql or "TBLPROPERTIES" in result.sql + + def test_create_table_with_constraint(self) -> None: + builder = OperationBuilder() + ops = [ + builder.catalog.add_catalog("cat_1", "analytics", op_id="op_1"), + builder.schema.add_schema("s1", "raw", "cat_1", op_id="op_2"), + builder.table.add_table("t1", "events", "s1", "delta", op_id="op_3"), + builder.column.add_column("c1", "t1", "id", "INT", nullable=False, op_id="op_4"), + builder.constraint.add_constraint( + "pk_1", "t1", "primary_key", ["c1"], name="pk_events", op_id="op_5" + ), + ] + result = _gen_with_mapping(ops) + assert "CREATE TABLE" in result.sql or "CONSTRAINT" in result.sql + + def test_create_table_with_row_filter(self) -> None: + builder = OperationBuilder() + ops = [ + builder.catalog.add_catalog("cat_1", "analytics", op_id="op_1"), + builder.schema.add_schema("s1", "raw", "cat_1", op_id="op_2"), + builder.table.add_table("t1", "events", "s1", "delta", op_id="op_3"), + builder.column.add_column("c1", "t1", "region", "STRING", op_id="op_4"), + builder.row_filter.add_row_filter( + "rf1", "t1", "region_filter", "region = 'US'", enabled=True, op_id="op_5" + ), + ] + result = _gen_with_mapping(ops) + assert "CREATE TABLE" in result.sql or "ROW FILTER" in result.sql + + def test_create_table_with_column_mask(self) -> None: + builder = OperationBuilder() + ops = [ + builder.catalog.add_catalog("cat_1", "analytics", op_id="op_1"), + builder.schema.add_schema("s1", "raw", "cat_1", op_id="op_2"), + builder.table.add_table("t1", "events", "s1", "delta", op_id="op_3"), + builder.column.add_column("c1", "t1", "ssn", "STRING", op_id="op_4"), + builder.column_mask.add_column_mask( + "cm1", + "t1", + "c1", + "ssn_mask", + "CASE WHEN is_account_group_member('admins') THEN ssn ELSE '***' END", + enabled=True, + op_id="op_5", + ), + ] + result = _gen_with_mapping(ops) + assert "CREATE TABLE" in result.sql or "MASK" in result.sql + + def test_create_table_with_reorder(self) -> None: + builder = OperationBuilder() + ops = [ + builder.catalog.add_catalog("cat_1", "analytics", op_id="op_1"), + builder.schema.add_schema("s1", "raw", "cat_1", op_id="op_2"), + builder.table.add_table("t1", "events", "s1", "delta", op_id="op_3"), + builder.column.add_column("c1", "t1", "id", "INT", op_id="op_4"), + builder.column.add_column("c2", "t1", "name", "STRING", op_id="op_5"), + builder.column.reorder_columns("t1", ["c2", "c1"], op_id="op_6"), + ] + result = _gen_with_mapping(ops) + assert "CREATE TABLE" in result.sql + + +# ── Batched ALTER: Tables ──────────────────────────────────────────── + + +@pytest.mark.integration +class TestBatchedAlterTable: + def test_alter_add_columns(self) -> None: + builder = OperationBuilder() + ops = ops_catalog_schema_table(builder) + [ + builder.column.add_column("c1", "t1", "id", "INT", op_id="op_4"), + builder.column.add_column("c2", "t1", "name", "STRING", op_id="op_5"), + ] + result = _gen_with_mapping(ops) + assert "`id`" in result.sql + assert "`name`" in result.sql + + def test_alter_with_property_ops(self) -> None: + builder = OperationBuilder() + ops = ops_catalog_schema_table(builder) + [ + builder.column.add_column("c1", "t1", "id", "INT", op_id="op_4"), + builder.table.set_table_property("t1", "delta.minReaderVersion", "2", op_id="op_5"), + builder.table.set_table_property("t1", "delta.minWriterVersion", "5", op_id="op_6"), + ] + result = _gen_with_mapping(ops) + assert "TBLPROPERTIES" in result.sql or "delta.minReaderVersion" in result.sql + + def test_alter_with_table_tags(self) -> None: + builder = OperationBuilder() + ops = ops_catalog_schema_table(builder) + [ + builder.column.add_column("c1", "t1", "id", "INT", op_id="op_4"), + builder.table.set_table_tag("t1", "pii", "true", op_id="op_5"), + builder.table.set_table_tag("t1", "team", "data-eng", op_id="op_6"), + ] + result = _gen_with_mapping(ops) + assert "TAGS" in result.sql or "pii" in result.sql + + def test_alter_with_constraint(self) -> None: + builder = OperationBuilder() + ops = ops_catalog_schema_table(builder) + [ + builder.column.add_column("c1", "t1", "id", "INT", nullable=False, op_id="op_4"), + builder.constraint.add_constraint( + "pk_1", "t1", "primary_key", ["c1"], name="pk_orders", op_id="op_5" + ), + ] + result = _gen_with_mapping(ops) + assert "CONSTRAINT" in result.sql or "PRIMARY KEY" in result.sql + + def test_alter_with_governance_ops(self) -> None: + builder = OperationBuilder() + ops = ops_catalog_schema_table(builder) + [ + builder.column.add_column("c1", "t1", "ssn", "STRING", op_id="op_4"), + builder.row_filter.add_row_filter( + "rf1", "t1", "region_filter", "region = 'US'", op_id="op_5" + ), + builder.column_mask.add_column_mask( + "cm1", + "t1", + "c1", + "ssn_mask", + "CASE WHEN true THEN ssn ELSE '***' END", + op_id="op_6", + ), + ] + result = _gen_with_mapping(ops) + assert len(result.statements) >= 1 + + def test_alter_mixed_operations(self) -> None: + builder = OperationBuilder() + ops = ops_catalog_schema_table(builder) + [ + builder.column.add_column("c1", "t1", "id", "INT", nullable=False, op_id="op_4"), + builder.column.add_column("c2", "t1", "email", "STRING", op_id="op_5"), + builder.table.set_table_property("t1", "delta.appendOnly", "true", op_id="op_6"), + builder.table.set_table_comment("t1", "Order records", op_id="op_7"), + builder.table.set_table_tag("t1", "domain", "sales", op_id="op_8"), + builder.constraint.add_constraint( + "pk_1", "t1", "primary_key", ["c1"], name="pk_orders", op_id="op_9" + ), + ] + result = _gen_with_mapping(ops) + assert "`id`" in result.sql + assert "`email`" in result.sql diff --git a/packages/python-sdk/tests/integration/test_sql_gen_locations.py b/packages/python-sdk/tests/integration/test_sql_gen_locations.py new file mode 100644 index 0000000..fcb4756 --- /dev/null +++ b/packages/python-sdk/tests/integration/test_sql_gen_locations.py @@ -0,0 +1,213 @@ +"""Integration tests for SQL generator location resolution. + +Covers: +- _resolve_table_location (lines 211-256) +- _resolve_managed_location (lines 258-305) +- All error paths: missing config, not found, no env, env not configured +""" + +import pytest + +from schemax.providers.unity.models import UnityState +from schemax.providers.unity.sql_generator import UnitySQLGenerator +from schemax.providers.unity.state_reducer import apply_operations +from tests.utils import OperationBuilder + + +def _empty(): + return UnityState(catalogs=[]) + + +# ── External Location Resolution ───────────────────────────────────── + + +@pytest.mark.integration +class TestExternalLocationResolution: + def _external_table_ops(self, builder, ext_loc_name, path=None): + options = {"external": True, "external_location_name": ext_loc_name} + if path: + options["path"] = path + return [ + builder.catalog.add_catalog("cat_1", "analytics", op_id="op_1"), + builder.schema.add_schema("s1", "raw", "cat_1", op_id="op_2"), + builder.table.add_table("t1", "events", "s1", "delta", options=options, op_id="op_3"), + builder.column.add_column("c1", "t1", "id", "INT", op_id="op_4"), + ] + + def test_resolve_with_path(self) -> None: + builder = OperationBuilder() + ops = self._external_table_ops(builder, "landing", "events/v1") + state = apply_operations(_empty(), ops) + gen = UnitySQLGenerator( + state, + external_locations={ + "landing": {"paths": {"dev": "abfss://landing@storage.dfs.core.windows.net/data"}} + }, + environment_name="dev", + ) + # Use generate_sql_for_operation for the table op to exercise location resolution + result = gen.generate_sql_for_operation(ops[2]) # add_table op + assert "LOCATION" in result.sql or "Resolved Location" in result.sql + assert "abfss://" in result.sql + + def test_resolve_without_path(self) -> None: + builder = OperationBuilder() + ops = self._external_table_ops(builder, "landing") + state = apply_operations(_empty(), ops) + gen = UnitySQLGenerator( + state, + external_locations={ + "landing": {"paths": {"dev": "abfss://landing@storage.dfs.core.windows.net/data/"}} + }, + environment_name="dev", + ) + result = gen.generate_sql_for_operation(ops[2]) # add_table op + assert "LOCATION" in result.sql or "Resolved Location" in result.sql + + def test_missing_config_returns_error(self) -> None: + builder = OperationBuilder() + ops = self._external_table_ops(builder, "missing") + state = apply_operations(_empty(), ops) + gen = UnitySQLGenerator(state) + result = gen.generate_sql_for_operation(ops[2]) + assert "externalLocations" in result.sql + + def test_location_not_found_returns_error(self) -> None: + builder = OperationBuilder() + ops = self._external_table_ops(builder, "missing") + state = apply_operations(_empty(), ops) + gen = UnitySQLGenerator( + state, + external_locations={"other": {"paths": {"dev": "abfss://x"}}}, + environment_name="dev", + ) + result = gen.generate_sql_for_operation(ops[2]) + assert "not found" in result.sql + + def test_no_environment_name_returns_error(self) -> None: + builder = OperationBuilder() + ops = self._external_table_ops(builder, "landing") + state = apply_operations(_empty(), ops) + gen = UnitySQLGenerator( + state, + external_locations={"landing": {"paths": {"dev": "abfss://x"}}}, + ) + result = gen.generate_sql_for_operation(ops[2]) + assert "environment name" in result.sql + + def test_env_not_configured_returns_error(self) -> None: + builder = OperationBuilder() + ops = self._external_table_ops(builder, "landing") + state = apply_operations(_empty(), ops) + gen = UnitySQLGenerator( + state, + external_locations={"landing": {"paths": {"prod": "abfss://x"}}}, + environment_name="dev", + ) + result = gen.generate_sql_for_operation(ops[2]) + assert "Configured environments" in result.sql + + +# ── Managed Location Resolution ────────────────────────────────────── + + +@pytest.mark.integration +class TestManagedLocationResolution: + def test_catalog_with_managed_location(self) -> None: + builder = OperationBuilder() + ops = [ + builder.catalog.add_catalog( + "cat_1", "warehouse", managed_location_name="ml1", op_id="op_1" + ), + ] + result_sql = _gen_sql_with_locations( + ops, + managed_locations={ + "ml1": {"paths": {"dev": "abfss://container@account.dfs.core.windows.net/ml1"}} + }, + environment_name="dev", + ) + assert "CREATE CATALOG" in result_sql + assert "MANAGED LOCATION" in result_sql + + def test_schema_with_managed_location(self) -> None: + builder = OperationBuilder() + ops = [ + builder.catalog.add_catalog("cat_1", "analytics", op_id="op_1"), + builder.schema.add_schema("s1", "raw", "cat_1", op_id="op_2"), + builder.schema.update_schema("s1", managed_location_name="ml1", op_id="op_3"), + ] + result_sql = _gen_sql_with_locations( + ops, + managed_locations={ + "ml1": {"paths": {"dev": "abfss://container@account.dfs.core.windows.net/raw"}} + }, + environment_name="dev", + ) + assert "CREATE SCHEMA" in result_sql + assert "MANAGED LOCATION" in result_sql + + def test_missing_config_raises(self) -> None: + builder = OperationBuilder() + ops = [ + builder.catalog.add_catalog( + "cat_1", "analytics", managed_location_name="missing", op_id="op_1" + ), + ] + state = apply_operations(_empty(), ops) + gen = UnitySQLGenerator(state) + with pytest.raises(ValueError, match="managedLocations"): + gen.generate_sql(ops) + + def test_location_not_found_raises(self) -> None: + builder = OperationBuilder() + ops = [ + builder.catalog.add_catalog( + "cat_1", "analytics", managed_location_name="missing", op_id="op_1" + ), + ] + state = apply_operations(_empty(), ops) + gen = UnitySQLGenerator( + state, + managed_locations={"other": {"paths": {"dev": "abfss://x"}}}, + environment_name="dev", + ) + with pytest.raises(ValueError, match="not found"): + gen.generate_sql(ops) + + def test_no_environment_name_raises(self) -> None: + builder = OperationBuilder() + ops = [ + builder.catalog.add_catalog( + "cat_1", "analytics", managed_location_name="ml1", op_id="op_1" + ), + ] + state = apply_operations(_empty(), ops) + gen = UnitySQLGenerator( + state, + managed_locations={"ml1": {"paths": {"dev": "abfss://x"}}}, + ) + with pytest.raises(ValueError, match="environment name"): + gen.generate_sql(ops) + + def test_env_not_configured_raises(self) -> None: + builder = OperationBuilder() + ops = [ + builder.catalog.add_catalog( + "cat_1", "analytics", managed_location_name="ml1", op_id="op_1" + ), + ] + state = apply_operations(_empty(), ops) + gen = UnitySQLGenerator( + state, + managed_locations={"ml1": {"paths": {"prod": "abfss://x"}}}, + environment_name="dev", + ) + with pytest.raises(ValueError, match="Configured environments"): + gen.generate_sql(ops) + + +def _gen_sql_with_locations(ops, **gen_kwargs): + state = apply_operations(_empty(), ops) + gen = UnitySQLGenerator(state, **gen_kwargs) + return gen.generate_sql(ops) diff --git a/packages/python-sdk/tests/integration/test_sql_gen_objects.py b/packages/python-sdk/tests/integration/test_sql_gen_objects.py new file mode 100644 index 0000000..8731bf9 --- /dev/null +++ b/packages/python-sdk/tests/integration/test_sql_gen_objects.py @@ -0,0 +1,488 @@ +"""Integration tests for SQL generation of various object types. + +Covers: drops, renames, views, functions, volumes, materialized views, +grants, governance (row filters, column masks), catalog mappings, +dependency-ordered generation. +""" + +import pytest + +from schemax.providers.unity.models import UnityState +from schemax.providers.unity.sql_generator import UnitySQLGenerator +from schemax.providers.unity.state_reducer import apply_operations +from tests.utils import OperationBuilder, ops_catalog_schema_table + + +def _empty(): + return UnityState(catalogs=[]) + + +def _gen_sql(ops: list, **gen_kwargs) -> str: + state = apply_operations(_empty(), ops) + gen = UnitySQLGenerator(state, **gen_kwargs) + return gen.generate_sql(ops) + + +def _gen_single(ops: list, target_op_index: int = -1) -> str: + state = apply_operations(_empty(), ops) + gen = UnitySQLGenerator(state) + return gen.generate_sql_for_operation(ops[target_op_index]).sql + + +def _gen_with_mapping(ops: list, **gen_kwargs): + state = apply_operations(_empty(), ops) + gen = UnitySQLGenerator(state, **gen_kwargs) + return gen.generate_sql_with_mapping(ops) + + +# ── Drop operations ────────────────────────────────────────────────── + + +@pytest.mark.integration +class TestDropOperationsSql: + def test_drop_table(self) -> None: + builder = OperationBuilder() + create_ops = ops_catalog_schema_table(builder) + drop_op = builder.table.drop_table("t1", op_id="op_4") + # Build state from create ops, then generate SQL for drop + state = apply_operations(_empty(), create_ops) + gen = UnitySQLGenerator(state) + result = gen.generate_sql_for_operation(drop_op) + assert "DROP TABLE" in result.sql + assert "`orders`" in result.sql + + def test_drop_schema(self) -> None: + builder = OperationBuilder() + ops = [ + builder.catalog.add_catalog("cat_1", "sales", op_id="op_1"), + builder.schema.add_schema("s1", "raw", "cat_1", op_id="op_2"), + builder.schema.drop_schema("s1", op_id="op_3"), + ] + sql = _gen_single(ops) + assert "DROP SCHEMA" in sql + + def test_drop_catalog(self) -> None: + builder = OperationBuilder() + ops = [ + builder.catalog.add_catalog("cat_1", "temp", op_id="op_1"), + builder.catalog.drop_catalog("cat_1", op_id="op_2"), + ] + sql = _gen_single(ops) + assert "DROP CATALOG" in sql + + def test_create_then_drop_cancels(self) -> None: + builder = OperationBuilder() + ops = [ + builder.catalog.add_catalog("cat_1", "temp", op_id="op_1"), + builder.schema.add_schema("s1", "raw", "cat_1", op_id="op_2"), + builder.table.add_table("t1", "events", "s1", "delta", op_id="op_3"), + builder.table.drop_table("t1", op_id="op_4"), + ] + sql = _gen_sql(ops) + assert "CREATE CATALOG" in sql + assert "CREATE SCHEMA" in sql + + def test_drop_view(self) -> None: + builder = OperationBuilder() + ops = [ + builder.catalog.add_catalog("cat_1", "analytics", op_id="op_1"), + builder.schema.add_schema("s1", "raw", "cat_1", op_id="op_2"), + builder.view.add_view("v1", "old_view", "s1", "SELECT 1", op_id="op_3"), + builder.view.drop_view("v1", op_id="op_4"), + ] + result = _gen_with_mapping(ops) + assert isinstance(result.sql, str) + + def test_drop_volume(self) -> None: + builder = OperationBuilder() + ops = [ + builder.catalog.add_catalog("cat_1", "analytics", op_id="op_1"), + builder.schema.add_schema("s1", "raw", "cat_1", op_id="op_2"), + builder.volume.add_volume("v1", "temp_files", "s1", "managed", op_id="op_3"), + builder.volume.drop_volume("v1", op_id="op_4"), + ] + sql = _gen_single(ops) + assert "DROP" in sql and "VOLUME" in sql + + def test_drop_materialized_view(self) -> None: + builder = OperationBuilder() + ops = [ + builder.catalog.add_catalog("cat_1", "analytics", op_id="op_1"), + builder.schema.add_schema("s1", "raw", "cat_1", op_id="op_2"), + builder.materialized_view.add_materialized_view( + "mv1", "daily_agg", "s1", "SELECT 1", op_id="op_3" + ), + builder.materialized_view.drop_materialized_view("mv1", op_id="op_4"), + ] + sql = _gen_single(ops) + assert "DROP" in sql and "MATERIALIZED VIEW" in sql + + def test_drop_column(self) -> None: + builder = OperationBuilder() + create_ops = ops_catalog_schema_table(builder) + [ + builder.column.add_column("c1", "t1", "temp_col", "STRING", op_id="op_4"), + ] + drop_op = builder.column.drop_column("c1", "t1", op_id="op_5") + state = apply_operations(_empty(), create_ops) + gen = UnitySQLGenerator(state) + result = gen.generate_sql_for_operation(drop_op) + assert "DROP COLUMN" in result.sql + assert "`temp_col`" in result.sql + + +# ── Rename operations ──────────────────────────────────────────────── + + +@pytest.mark.integration +class TestRenameOperationsSql: + def test_rename_table(self) -> None: + builder = OperationBuilder() + ops = ops_catalog_schema_table(builder) + [ + builder.table.rename_table("t1", "order_items", "orders", op_id="op_4"), + ] + sql = _gen_single(ops) + assert "RENAME TO" in sql + assert "`order_items`" in sql + + def test_rename_column(self) -> None: + builder = OperationBuilder() + ops = ops_catalog_schema_table(builder) + [ + builder.column.add_column("c1", "t1", "email", "STRING", op_id="op_4"), + builder.column.rename_column("c1", "t1", "contact_email", "email", op_id="op_5"), + ] + sql = _gen_single(ops) + assert "RENAME COLUMN" in sql + assert "`contact_email`" in sql + + def test_rename_catalog(self) -> None: + builder = OperationBuilder() + ops = [ + builder.catalog.add_catalog("cat_1", "old_name", op_id="op_1"), + builder.catalog.rename_catalog("cat_1", "new_name", "old_name", op_id="op_2"), + ] + sql = _gen_single(ops) + assert "RENAME" in sql and "`new_name`" in sql + + def test_rename_schema(self) -> None: + builder = OperationBuilder() + ops = [ + builder.catalog.add_catalog("cat_1", "sales", op_id="op_1"), + builder.schema.add_schema("s1", "old_schema", "cat_1", op_id="op_2"), + builder.schema.rename_schema("s1", "new_schema", "old_schema", op_id="op_3"), + ] + sql = _gen_single(ops) + assert "RENAME" in sql and "`new_schema`" in sql + + +# ── Views ──────────────────────────────────────────────────────────── + + +@pytest.mark.integration +class TestViewSqlGeneration: + def test_create_view(self) -> None: + builder = OperationBuilder() + ops = [ + builder.catalog.add_catalog("cat_1", "analytics", op_id="op_1"), + builder.schema.add_schema("s1", "raw", "cat_1", op_id="op_2"), + builder.view.add_view( + "v1", + "active_users", + "s1", + "SELECT * FROM users WHERE active = true", + comment="Active users only", + op_id="op_3", + ), + ] + sql = _gen_single(ops, target_op_index=2) + assert "CREATE" in sql and "VIEW" in sql and "`active_users`" in sql + + +# ── Functions ──────────────────────────────────────────────────────── + + +@pytest.mark.integration +class TestFunctionSqlGeneration: + def test_create_function_with_params(self) -> None: + builder = OperationBuilder() + ops = [ + builder.catalog.add_catalog("cat_1", "analytics", op_id="op_1"), + builder.schema.add_schema("s1", "raw", "cat_1", op_id="op_2"), + builder.function.add_function( + "f1", + "add_numbers", + "s1", + "SQL", + "INT", + "a + b", + parameters=[{"name": "a", "type": "INT"}, {"name": "b", "type": "INT"}], + comment="Adds two numbers", + op_id="op_3", + ), + ] + sql = _gen_single(ops, target_op_index=2) + assert "CREATE" in sql and "FUNCTION" in sql and "`add_numbers`" in sql + + +# ── Volumes ────────────────────────────────────────────────────────── + + +@pytest.mark.integration +class TestVolumeSqlGeneration: + def test_create_external_volume(self) -> None: + builder = OperationBuilder() + ops = [ + builder.catalog.add_catalog("cat_1", "analytics", op_id="op_1"), + builder.schema.add_schema("s1", "raw", "cat_1", op_id="op_2"), + builder.volume.add_volume( + "v1", + "raw_files", + "s1", + "external", + location="abfss://container@account.dfs.core.windows.net/raw", + comment="Raw file storage", + op_id="op_3", + ), + ] + sql = _gen_single(ops, target_op_index=2) + assert "CREATE" in sql and "VOLUME" in sql + + +# ── Grants ─────────────────────────────────────────────────────────── + + +@pytest.mark.integration +class TestGrantSqlGeneration: + def test_grant_on_schema(self) -> None: + builder = OperationBuilder() + ops = [ + builder.catalog.add_catalog("cat_1", "analytics", op_id="op_1"), + builder.schema.add_schema("s1", "raw", "cat_1", op_id="op_2"), + builder.grant.add_grant( + "schema", "s1", "data_team", ["CREATE TABLE", "SELECT"], op_id="op_3" + ), + ] + sql = _gen_sql(ops) + assert "GRANT" in sql and "data_team" in sql + + def test_grant_on_table(self) -> None: + builder = OperationBuilder() + ops = ops_catalog_schema_table(builder) + [ + builder.grant.add_grant("table", "t1", "analysts", ["SELECT", "MODIFY"], op_id="op_4"), + ] + sql = _gen_sql(ops) + assert "GRANT" in sql and "analysts" in sql + + def test_revoke_grant(self) -> None: + builder = OperationBuilder() + ops = [ + builder.catalog.add_catalog("cat_1", "analytics", op_id="op_1"), + builder.schema.add_schema("s1", "raw", "cat_1", op_id="op_2"), + builder.grant.add_grant("schema", "s1", "data_team", ["CREATE TABLE"], op_id="op_3"), + builder.grant.revoke_grant("schema", "s1", "data_team", op_id="op_4"), + ] + sql = _gen_sql(ops) + assert "REVOKE" in sql + + +# ── Governance ─────────────────────────────────────────────────────── + + +@pytest.mark.integration +class TestGovernanceSqlGeneration: + def test_add_row_filter(self) -> None: + builder = OperationBuilder() + ops = ops_catalog_schema_table(builder) + [ + builder.column.add_column("c1", "t1", "region", "STRING", op_id="op_4"), + builder.row_filter.add_row_filter( + "rf1", + "t1", + "region_filter", + "region = current_user()", + enabled=True, + description="Filter by region", + op_id="op_5", + ), + ] + sql = _gen_single(ops) + assert "ROW FILTER" in sql or "ALTER TABLE" in sql + + def test_add_column_mask(self) -> None: + builder = OperationBuilder() + ops = ops_catalog_schema_table(builder) + [ + builder.column.add_column("c1", "t1", "ssn", "STRING", op_id="op_4"), + builder.column_mask.add_column_mask( + "cm1", + "t1", + "c1", + "mask_ssn", + "CASE WHEN is_account_group_member('admins') THEN ssn ELSE 'XXX' END", + enabled=True, + description="Mask SSN", + op_id="op_5", + ), + ] + sql = _gen_single(ops) + assert "MASK" in sql or "ALTER TABLE" in sql + + def test_remove_row_filter(self) -> None: + builder = OperationBuilder() + ops = ops_catalog_schema_table(builder) + [ + builder.column.add_column("c1", "t1", "region", "STRING", op_id="op_4"), + builder.row_filter.add_row_filter( + "rf1", "t1", "region_filter", "region = 'US'", op_id="op_5" + ), + builder.row_filter.remove_row_filter("rf1", "t1", op_id="op_6"), + ] + sql = _gen_single(ops) + assert "ROW FILTER" in sql or "DROP" in sql or "NONE" in sql + + def test_remove_column_mask(self) -> None: + builder = OperationBuilder() + ops = ops_catalog_schema_table(builder) + [ + builder.column.add_column("c1", "t1", "ssn", "STRING", op_id="op_4"), + builder.column_mask.add_column_mask( + "cm1", + "t1", + "c1", + "mask_ssn", + "CASE WHEN true THEN ssn ELSE 'X' END", + op_id="op_5", + ), + builder.column_mask.remove_column_mask("cm1", "t1", "c1", op_id="op_6"), + ] + sql = _gen_single(ops) + assert "MASK" in sql or "DROP" in sql or "NONE" in sql + + +# ── Catalog Mapping ────────────────────────────────────────────────── + + +@pytest.mark.integration +class TestCatalogMappingSql: + def test_create_with_mapping(self) -> None: + builder = OperationBuilder() + ops = [ + builder.catalog.add_catalog("cat_1", "demo", op_id="op_1"), + builder.schema.add_schema("s1", "raw", "cat_1", op_id="op_2"), + builder.table.add_table("t1", "orders", "s1", "delta", op_id="op_3"), + builder.column.add_column("c1", "t1", "id", "INT", op_id="op_4"), + ] + sql = _gen_sql(ops, catalog_name_mapping={"demo": "dev_demo"}) + assert "`dev_demo`" in sql + + def test_alter_with_mapping(self) -> None: + builder = OperationBuilder() + ops = [ + builder.catalog.add_catalog("cat_1", "demo", op_id="op_1"), + builder.schema.add_schema("s1", "raw", "cat_1", op_id="op_2"), + builder.table.add_table("t1", "orders", "s1", "delta", op_id="op_3"), + builder.column.add_column("c1", "t1", "id", "INT", op_id="op_4"), + builder.table.set_table_comment("t1", "Order table", op_id="op_5"), + ] + state = apply_operations(_empty(), ops) + gen = UnitySQLGenerator(state, catalog_name_mapping={"demo": "dev_demo"}) + result = gen.generate_sql_for_operation(ops[-1]) + assert "`dev_demo`" in result.sql + + +# ── Dependency-ordered SQL ─────────────────────────────────────────── + + +@pytest.mark.integration +class TestDependencyOrderedSql: + def test_out_of_order_operations(self) -> None: + builder = OperationBuilder() + ops = [ + builder.table.add_table("t1", "events", "s1", "delta", op_id="op_3"), + builder.schema.add_schema("s1", "raw", "cat_1", op_id="op_2"), + builder.catalog.add_catalog("cat_1", "analytics", op_id="op_1"), + builder.column.add_column("c1", "t1", "id", "INT", op_id="op_4"), + ] + state = apply_operations(_empty(), ops) + gen = UnitySQLGenerator(state) + result = gen.generate_sql_with_dependencies(ops) + assert len(result.statements) >= 1 + + def test_with_views_dependency(self) -> None: + builder = OperationBuilder() + ops = [ + builder.catalog.add_catalog("cat_1", "analytics", op_id="op_1"), + builder.schema.add_schema("s1", "raw", "cat_1", op_id="op_2"), + builder.table.add_table("t1", "events", "s1", "delta", op_id="op_3"), + builder.column.add_column("c1", "t1", "id", "INT", op_id="op_4"), + builder.view.add_view( + "v1", + "events_summary", + "s1", + "SELECT COUNT(*) FROM events", + op_id="op_5", + ), + ] + state = apply_operations(_empty(), ops) + gen = UnitySQLGenerator(state) + result = gen.generate_sql_with_dependencies(ops) + assert len(result.statements) >= 2 + + +# ── Complex schema ─────────────────────────────────────────────────── + + +@pytest.mark.integration +class TestComplexSchemaGeneration: + def test_full_schema_sql(self) -> None: + builder = OperationBuilder() + ops = [ + builder.catalog.add_catalog("cat_1", "ecommerce", op_id="op_01"), + builder.schema.add_schema("s1", "sales", "cat_1", op_id="op_02"), + builder.table.add_table("t1", "orders", "s1", "delta", op_id="op_03"), + builder.column.add_column( + "c1", "t1", "order_id", "BIGINT", nullable=False, op_id="op_04" + ), + builder.column.add_column("c2", "t1", "amount", "DECIMAL(10,2)", op_id="op_05"), + builder.column.add_column("c3", "t1", "region", "STRING", op_id="op_06"), + builder.constraint.add_constraint( + "pk1", "t1", "primary_key", ["c1"], name="pk_orders", op_id="op_07" + ), + builder.table.set_table_property( + "t1", "delta.autoOptimize.optimizeWrite", "true", op_id="op_08" + ), + builder.table.add_table("t2", "customers", "s1", "delta", op_id="op_09"), + builder.column.add_column("c4", "t2", "id", "BIGINT", nullable=False, op_id="op_10"), + builder.column.add_column("c5", "t2", "ssn", "STRING", op_id="op_11"), + builder.column_mask.add_column_mask( + "cm1", + "t2", + "c5", + "mask_ssn", + "CASE WHEN is_account_group_member('admins') THEN ssn ELSE '***' END", + op_id="op_12", + ), + builder.view.add_view( + "v1", + "order_summary", + "s1", + "SELECT customer_id, SUM(amount) FROM orders GROUP BY customer_id", + op_id="op_13", + ), + builder.function.add_function( + "f1", + "format_amount", + "s1", + "SQL", + "STRING", + "CONCAT('$', CAST(amount AS STRING))", + parameters=[{"name": "amount", "type": "DECIMAL(10,2)"}], + op_id="op_14", + ), + builder.volume.add_volume( + "vol1", "raw_files", "s1", "managed", comment="Raw data", op_id="op_15" + ), + builder.grant.add_grant( + "catalog", "cat_1", "data_team", ["USE CATALOG"], op_id="op_16" + ), + ] + result = _gen_with_mapping(ops) + assert "CREATE CATALOG" in result.sql + assert "CREATE SCHEMA" in result.sql + assert "orders" in result.sql.lower() + assert len(result.statements) >= 5 diff --git a/packages/python-sdk/tests/integration/test_state_reducer_edges.py b/packages/python-sdk/tests/integration/test_state_reducer_edges.py new file mode 100644 index 0000000..d58706b --- /dev/null +++ b/packages/python-sdk/tests/integration/test_state_reducer_edges.py @@ -0,0 +1,385 @@ +"""Integration tests for state reducer edge cases. + +Covers uncovered branches in state_reducer.py: +- unset_table_tag, unset_view_tag, unset_column_tag +- change_column_type, set_nullable, set_column_comment +- reorder_columns with missing columns +- set_column_tag on columns with None tags +- update_view, update_materialized_view +- add_constraint with insertAt +""" + +import pytest + +from schemax.providers.unity.models import UnityState +from schemax.providers.unity.state_reducer import apply_operations +from tests.utils import OperationBuilder + + +def _empty(): + return UnityState(catalogs=[]) + + +def _base_table_ops(builder): + """Create base ops for a catalog + schema + table with columns.""" + return [ + builder.catalog.add_catalog("cat_1", "analytics", op_id="op_1"), + builder.schema.add_schema("s1", "raw", "cat_1", op_id="op_2"), + builder.table.add_table("t1", "events", "s1", "delta", op_id="op_3"), + builder.column.add_column("c1", "t1", "id", "INT", op_id="op_4"), + builder.column.add_column("c2", "t1", "name", "STRING", op_id="op_5"), + builder.column.add_column("c3", "t1", "ts", "TIMESTAMP", op_id="op_6"), + ] + + +# ── Tag Operations ────────────────────────────────────────────────── + + +@pytest.mark.integration +class TestUnsetTableTag: + def test_unset_existing_tag(self) -> None: + builder = OperationBuilder() + ops = _base_table_ops(builder) + [ + builder.table.set_table_tag("t1", "pii", "true", op_id="op_7"), + builder.table.unset_table_tag("t1", "pii", op_id="op_8"), + ] + state = apply_operations(_empty(), ops) + table = state.catalogs[0].schemas[0].tables[0] + assert "pii" not in table.tags + + def test_unset_nonexistent_tag(self) -> None: + builder = OperationBuilder() + ops = _base_table_ops(builder) + [ + builder.table.unset_table_tag("t1", "nonexistent", op_id="op_7"), + ] + # Should not raise + state = apply_operations(_empty(), ops) + table = state.catalogs[0].schemas[0].tables[0] + assert "nonexistent" not in table.tags + + +@pytest.mark.integration +class TestSetAndUnsetColumnTag: + def test_set_column_tag(self) -> None: + builder = OperationBuilder() + ops = _base_table_ops(builder) + [ + # col builder: (col_id, table_id, tag_name, tag_value) + builder.column.set_column_tag("c1", "t1", "pii", "true", op_id="op_7"), + ] + state = apply_operations(_empty(), ops) + col = state.catalogs[0].schemas[0].tables[0].columns[0] + assert col.tags["pii"] == "true" + + def test_unset_column_tag(self) -> None: + builder = OperationBuilder() + ops = _base_table_ops(builder) + [ + builder.column.set_column_tag("c1", "t1", "pii", "true", op_id="op_7"), + # unset_column_tag: (col_id, table_id, tag_name) + builder.column.unset_column_tag("c1", "t1", "pii", op_id="op_8"), + ] + state = apply_operations(_empty(), ops) + col = state.catalogs[0].schemas[0].tables[0].columns[0] + assert "pii" not in (col.tags or {}) + + +# ── Column Operations ─────────────────────────────────────────────── + + +@pytest.mark.integration +class TestColumnTypeChange: + def test_change_column_type(self) -> None: + builder = OperationBuilder() + ops = _base_table_ops(builder) + [ + # change_column_type: (col_id, table_id, new_type) + builder.column.change_column_type("c1", "t1", "BIGINT", op_id="op_7"), + ] + state = apply_operations(_empty(), ops) + col = state.catalogs[0].schemas[0].tables[0].columns[0] + assert col.type == "BIGINT" + + def test_change_column_type_string_to_binary(self) -> None: + builder = OperationBuilder() + ops = _base_table_ops(builder) + [ + builder.column.change_column_type("c2", "t1", "BINARY", op_id="op_7"), + ] + state = apply_operations(_empty(), ops) + col = state.catalogs[0].schemas[0].tables[0].columns[1] + assert col.type == "BINARY" + + +@pytest.mark.integration +class TestSetNullable: + def test_set_nullable_false(self) -> None: + builder = OperationBuilder() + ops = _base_table_ops(builder) + [ + # set_nullable: (col_id, table_id, nullable) + builder.column.set_nullable("c1", "t1", False, op_id="op_7"), + ] + state = apply_operations(_empty(), ops) + col = state.catalogs[0].schemas[0].tables[0].columns[0] + assert col.nullable is False + + def test_set_nullable_true(self) -> None: + builder = OperationBuilder() + ops = _base_table_ops(builder) + [ + builder.column.set_nullable("c1", "t1", False, op_id="op_7"), + builder.column.set_nullable("c1", "t1", True, op_id="op_8"), + ] + state = apply_operations(_empty(), ops) + col = state.catalogs[0].schemas[0].tables[0].columns[0] + assert col.nullable is True + + +@pytest.mark.integration +class TestSetColumnComment: + def test_set_comment(self) -> None: + builder = OperationBuilder() + ops = _base_table_ops(builder) + [ + # set_column_comment: (col_id, table_id, comment) + builder.column.set_column_comment("c1", "t1", "Primary key", op_id="op_7"), + ] + state = apply_operations(_empty(), ops) + col = state.catalogs[0].schemas[0].tables[0].columns[0] + assert col.comment == "Primary key" + + def test_update_comment(self) -> None: + builder = OperationBuilder() + ops = _base_table_ops(builder) + [ + builder.column.set_column_comment("c1", "t1", "First", op_id="op_7"), + builder.column.set_column_comment("c1", "t1", "Updated", op_id="op_8"), + ] + state = apply_operations(_empty(), ops) + col = state.catalogs[0].schemas[0].tables[0].columns[0] + assert col.comment == "Updated" + + +# ── Reorder Columns ───────────────────────────────────────────────── + + +@pytest.mark.integration +class TestReorderColumns: + def test_reorder_all_columns(self) -> None: + builder = OperationBuilder() + ops = _base_table_ops(builder) + [ + builder.column.reorder_columns("t1", ["c3", "c1", "c2"], op_id="op_7"), + ] + state = apply_operations(_empty(), ops) + cols = state.catalogs[0].schemas[0].tables[0].columns + assert [c.id for c in cols] == ["c3", "c1", "c2"] + + def test_reorder_partial(self) -> None: + """Columns not in the order list go to the end.""" + builder = OperationBuilder() + ops = _base_table_ops(builder) + [ + builder.column.reorder_columns("t1", ["c2", "c1"], op_id="op_7"), + ] + state = apply_operations(_empty(), ops) + cols = state.catalogs[0].schemas[0].tables[0].columns + # c2 and c1 first, c3 last (not in order list) + assert cols[0].id == "c2" + assert cols[1].id == "c1" + assert cols[2].id == "c3" + + +# ── View Operations ───────────────────────────────────────────────── + + +@pytest.mark.integration +class TestViewOperations: + def test_update_view_definition(self) -> None: + builder = OperationBuilder() + ops = [ + builder.catalog.add_catalog("cat_1", "analytics", op_id="op_1"), + builder.schema.add_schema("s1", "raw", "cat_1", op_id="op_2"), + builder.view.add_view("v1", "user_summary", "s1", "SELECT 1", op_id="op_3"), + builder.view.update_view("v1", definition="SELECT id, name FROM users", op_id="op_4"), + ] + state = apply_operations(_empty(), ops) + view = state.catalogs[0].schemas[0].views[0] + assert view.definition == "SELECT id, name FROM users" + + def test_set_view_tag(self) -> None: + builder = OperationBuilder() + ops = [ + builder.catalog.add_catalog("cat_1", "analytics", op_id="op_1"), + builder.schema.add_schema("s1", "raw", "cat_1", op_id="op_2"), + builder.view.add_view("v1", "user_summary", "s1", "SELECT 1", op_id="op_3"), + builder.view.set_view_tag("v1", "domain", "analytics", op_id="op_4"), + ] + state = apply_operations(_empty(), ops) + view = state.catalogs[0].schemas[0].views[0] + assert view.tags["domain"] == "analytics" + + def test_unset_view_tag(self) -> None: + builder = OperationBuilder() + ops = [ + builder.catalog.add_catalog("cat_1", "analytics", op_id="op_1"), + builder.schema.add_schema("s1", "raw", "cat_1", op_id="op_2"), + builder.view.add_view("v1", "user_summary", "s1", "SELECT 1", op_id="op_3"), + builder.view.set_view_tag("v1", "domain", "analytics", op_id="op_4"), + builder.view.unset_view_tag("v1", "domain", op_id="op_5"), + ] + state = apply_operations(_empty(), ops) + view = state.catalogs[0].schemas[0].views[0] + assert "domain" not in view.tags + + def test_rename_view(self) -> None: + builder = OperationBuilder() + ops = [ + builder.catalog.add_catalog("cat_1", "analytics", op_id="op_1"), + builder.schema.add_schema("s1", "raw", "cat_1", op_id="op_2"), + builder.view.add_view("v1", "user_summary", "s1", "SELECT 1", op_id="op_3"), + # rename_view: (view_id, new_name, old_name) + builder.view.rename_view("v1", "user_overview", "user_summary", op_id="op_4"), + ] + state = apply_operations(_empty(), ops) + view = state.catalogs[0].schemas[0].views[0] + assert view.name == "user_overview" + + +# ── Materialized View Operations ──────────────────────────────────── + + +@pytest.mark.integration +class TestMaterializedViewOperations: + def test_update_materialized_view(self) -> None: + builder = OperationBuilder() + ops = [ + builder.catalog.add_catalog("cat_1", "analytics", op_id="op_1"), + builder.schema.add_schema("s1", "raw", "cat_1", op_id="op_2"), + builder.materialized_view.add_materialized_view( + "mv1", "agg_users", "s1", "SELECT count(*) FROM users", op_id="op_3" + ), + builder.materialized_view.update_materialized_view( + "mv1", + definition="SELECT count(*) as cnt FROM users", + op_id="op_4", + ), + ] + state = apply_operations(_empty(), ops) + mv = state.catalogs[0].schemas[0].materialized_views[0] + assert mv.definition == "SELECT count(*) as cnt FROM users" + + def test_set_materialized_view_comment(self) -> None: + builder = OperationBuilder() + ops = [ + builder.catalog.add_catalog("cat_1", "analytics", op_id="op_1"), + builder.schema.add_schema("s1", "raw", "cat_1", op_id="op_2"), + builder.materialized_view.add_materialized_view( + "mv1", "agg_users", "s1", "SELECT count(*) FROM users", op_id="op_3" + ), + builder.materialized_view.set_materialized_view_comment( + "mv1", "Aggregated user counts", op_id="op_4" + ), + ] + state = apply_operations(_empty(), ops) + mv = state.catalogs[0].schemas[0].materialized_views[0] + assert mv.comment == "Aggregated user counts" + + +# ── Constraint with insertAt ──────────────────────────────────────── + + +@pytest.mark.integration +class TestConstraintInsertAt: + def test_add_constraint_at_position(self) -> None: + builder = OperationBuilder() + ops = _base_table_ops(builder) + [ + builder.constraint.add_constraint( + "ck1", "t1", "check", ["c1"], name="ck_positive", expression="id > 0", op_id="op_7" + ), + builder.constraint.add_constraint( + "pk1", "t1", "primary_key", ["c1"], name="pk_events", insertAt=0, op_id="op_8" + ), + ] + state = apply_operations(_empty(), ops) + constraints = state.catalogs[0].schemas[0].tables[0].constraints + assert len(constraints) == 2 + # pk inserted at position 0 + assert constraints[0].id == "pk1" + assert constraints[1].id == "ck1" + + def test_drop_constraint(self) -> None: + builder = OperationBuilder() + ops = _base_table_ops(builder) + [ + builder.constraint.add_constraint( + "pk1", "t1", "primary_key", ["c1"], name="pk_events", op_id="op_7" + ), + builder.constraint.drop_constraint("pk1", "t1", op_id="op_8"), + ] + state = apply_operations(_empty(), ops) + constraints = state.catalogs[0].schemas[0].tables[0].constraints + assert len(constraints) == 0 + + +# ── Row Filter and Column Mask ────────────────────────────────────── + + +@pytest.mark.integration +class TestRowFilterAndColumnMask: + def test_update_row_filter(self) -> None: + builder = OperationBuilder() + ops = _base_table_ops(builder) + [ + builder.row_filter.add_row_filter( + "rf1", "t1", "region_filter", "region = 'US'", enabled=True, op_id="op_7" + ), + # update_row_filter: (filter_id, table_id, **kwargs) + builder.row_filter.update_row_filter( + "rf1", "t1", op_id="op_8", udfExpression="region IN ('US', 'CA')" + ), + ] + state = apply_operations(_empty(), ops) + rf = state.catalogs[0].schemas[0].tables[0].row_filters[0] + assert "CA" in rf.udf_expression + + def test_remove_row_filter(self) -> None: + builder = OperationBuilder() + ops = _base_table_ops(builder) + [ + builder.row_filter.add_row_filter( + "rf1", "t1", "region_filter", "region = 'US'", enabled=True, op_id="op_7" + ), + builder.row_filter.remove_row_filter("rf1", "t1", op_id="op_8"), + ] + state = apply_operations(_empty(), ops) + assert len(state.catalogs[0].schemas[0].tables[0].row_filters) == 0 + + def test_update_column_mask(self) -> None: + builder = OperationBuilder() + ops = _base_table_ops(builder) + [ + builder.column_mask.add_column_mask( + "cm1", + "t1", + "c2", + "name_mask", + "CASE WHEN true THEN name ELSE '***' END", + enabled=True, + op_id="op_7", + ), + # update_column_mask: (mask_id, table_id, **kwargs) + builder.column_mask.update_column_mask( + "cm1", + "t1", + op_id="op_8", + maskFunction="CASE WHEN is_admin() THEN name ELSE 'REDACTED' END", + ), + ] + state = apply_operations(_empty(), ops) + cm = state.catalogs[0].schemas[0].tables[0].column_masks[0] + assert "REDACTED" in cm.mask_function + + def test_remove_column_mask(self) -> None: + builder = OperationBuilder() + ops = _base_table_ops(builder) + [ + builder.column_mask.add_column_mask( + "cm1", + "t1", + "c2", + "name_mask", + "CASE WHEN true THEN name ELSE '***' END", + enabled=True, + op_id="op_7", + ), + builder.column_mask.remove_column_mask("cm1", "t1", "c2", op_id="op_8"), + ] + state = apply_operations(_empty(), ops) + assert len(state.catalogs[0].schemas[0].tables[0].column_masks) == 0 diff --git a/packages/python-sdk/tests/integration/test_validate_error_paths.py b/packages/python-sdk/tests/integration/test_validate_error_paths.py new file mode 100644 index 0000000..ce3ebb1 --- /dev/null +++ b/packages/python-sdk/tests/integration/test_validate_error_paths.py @@ -0,0 +1,200 @@ +"""Integration tests for validate command error paths. + +Covers: +- State validation failure (invalid state structure) +- Dependency validation (circular deps, warnings) +- Stale snapshot detection +- Project summary output (catalogs, schemas, tables) +- JSON envelope for validation failures +""" + +import json +from pathlib import Path + +import pytest + +from schemax.core.storage import ( + append_ops, + create_snapshot, + ensure_project_file, +) +from tests.utils import OperationBuilder +from tests.utils.cli_helpers import invoke_cli + + +@pytest.mark.integration +class TestValidateProjectSummary: + """Test the non-JSON summary output paths.""" + + def test_multi_schema_summary(self, temp_workspace: Path) -> None: + ensure_project_file(temp_workspace, provider_id="unity") + builder = OperationBuilder() + append_ops( + temp_workspace, + [ + builder.catalog.add_catalog("cat_1", "analytics", op_id="op_1"), + builder.schema.add_schema("s1", "raw", "cat_1", op_id="op_2"), + builder.schema.add_schema("s2", "curated", "cat_1", op_id="op_3"), + builder.table.add_table("t1", "users", "s1", "delta", op_id="op_4"), + builder.column.add_column("c1", "t1", "id", "INT", op_id="op_5"), + builder.table.add_table("t2", "orders", "s2", "delta", op_id="op_6"), + builder.column.add_column("c2", "t2", "order_id", "INT", op_id="op_7"), + ], + ) + result = invoke_cli("validate", str(temp_workspace)) + assert result.exit_code == 0 + assert "Catalogs:" in result.output + assert "Schemas:" in result.output + assert "Tables:" in result.output + + def test_validate_with_views(self, temp_workspace: Path) -> None: + ensure_project_file(temp_workspace, provider_id="unity") + builder = OperationBuilder() + append_ops( + temp_workspace, + [ + builder.catalog.add_catalog("cat_1", "analytics", op_id="op_1"), + builder.schema.add_schema("s1", "raw", "cat_1", op_id="op_2"), + builder.table.add_table("t1", "users", "s1", "delta", op_id="op_3"), + builder.column.add_column("c1", "t1", "id", "INT", op_id="op_4"), + builder.view.add_view( + "v1", "user_summary", "s1", "SELECT * FROM users", op_id="op_5" + ), + ], + ) + result = invoke_cli("validate", str(temp_workspace)) + assert result.exit_code == 0 + + def test_validate_with_dependencies(self, temp_workspace: Path) -> None: + """Validate a project with view dependencies.""" + ensure_project_file(temp_workspace, provider_id="unity") + builder = OperationBuilder() + append_ops( + temp_workspace, + [ + builder.catalog.add_catalog("cat_1", "analytics", op_id="op_1"), + builder.schema.add_schema("s1", "raw", "cat_1", op_id="op_2"), + builder.table.add_table("t1", "users", "s1", "delta", op_id="op_3"), + builder.column.add_column("c1", "t1", "id", "INT", op_id="op_4"), + builder.view.add_view( + "v1", + "user_summary", + "s1", + "SELECT * FROM users", + dependencies=["t1"], + op_id="op_5", + ), + ], + ) + result = invoke_cli("validate", str(temp_workspace)) + assert result.exit_code == 0 + # Should show dependency validation passing + assert "depend" in result.output.lower() or "valid" in result.output.lower() + + +@pytest.mark.integration +class TestValidateStaleSnapshots: + """Test stale snapshot detection during validation.""" + + def test_stale_snapshot_warning(self, temp_workspace: Path) -> None: + """Validate detects stale snapshots after changelog modification.""" + ensure_project_file(temp_workspace, provider_id="unity") + builder = OperationBuilder() + append_ops( + temp_workspace, + [ + builder.catalog.add_catalog("cat_1", "analytics", op_id="op_1"), + builder.schema.add_schema("s1", "raw", "cat_1", op_id="op_2"), + ], + ) + create_snapshot(temp_workspace, name="S1", version="v0.1.0") + + # Add more ops after snapshot - this makes the snapshot stale + append_ops( + temp_workspace, + [ + builder.table.add_table("t1", "users", "s1", "delta", op_id="op_3"), + builder.column.add_column("c1", "t1", "id", "INT", op_id="op_4"), + ], + ) + create_snapshot(temp_workspace, name="S2", version="v0.2.0") + + # Validate should still pass (stale snapshots are warnings, not errors) + result = invoke_cli("validate", str(temp_workspace)) + assert result.exit_code == 0 + + def test_json_with_stale_snapshots(self, temp_workspace: Path) -> None: + """JSON output includes staleSnapshots field.""" + ensure_project_file(temp_workspace, provider_id="unity") + builder = OperationBuilder() + append_ops( + temp_workspace, + [ + builder.catalog.add_catalog("cat_1", "demo", op_id="op_1"), + ], + ) + create_snapshot(temp_workspace, name="S1", version="v0.1.0") + result = invoke_cli("validate", "--json", str(temp_workspace)) + assert result.exit_code == 0 + data = json.loads(result.output) + # JSON envelope wraps in {data: {valid: ...}} or direct {valid: ...} + inner = data.get("data", data) + assert inner.get("valid") is True + assert "staleSnapshots" in inner or "warnings" in inner + + +@pytest.mark.integration +class TestValidateJsonErrors: + """Test JSON envelope output for validation failures.""" + + def test_no_project_json_output(self, temp_workspace: Path) -> None: + """JSON output when project file doesn't exist.""" + result = invoke_cli("validate", "--json", str(temp_workspace)) + assert result.exit_code == 1 + + def test_valid_project_json_envelope(self, temp_workspace: Path) -> None: + """Verify JSON envelope structure for valid project.""" + ensure_project_file(temp_workspace, provider_id="unity") + builder = OperationBuilder() + append_ops( + temp_workspace, + [ + builder.catalog.add_catalog("cat_1", "demo", op_id="op_1"), + builder.schema.add_schema("s1", "raw", "cat_1", op_id="op_2"), + ], + ) + result = invoke_cli("validate", "--json", str(temp_workspace)) + assert result.exit_code == 0 + data = json.loads(result.output) + # JSON envelope wraps in {data: {valid: ...}} or direct {valid: ...} + inner = data.get("data", data) + assert inner["valid"] is True + assert inner["errors"] == [] + + +@pytest.mark.integration +class TestValidateDependencyWarnings: + """Test dependency validation produces warnings for missing refs.""" + + def test_view_with_missing_dependency(self, temp_workspace: Path) -> None: + """View referencing non-existent table shows warning.""" + ensure_project_file(temp_workspace, provider_id="unity") + builder = OperationBuilder() + append_ops( + temp_workspace, + [ + builder.catalog.add_catalog("cat_1", "analytics", op_id="op_1"), + builder.schema.add_schema("s1", "raw", "cat_1", op_id="op_2"), + builder.view.add_view( + "v1", + "orphan_view", + "s1", + "SELECT * FROM nonexistent", + dependencies=["nonexistent_table_id"], + op_id="op_3", + ), + ], + ) + result = invoke_cli("validate", str(temp_workspace)) + # Should still pass (missing deps are warnings, not errors) + assert result.exit_code == 0 diff --git a/packages/python-sdk/tests/integration/test_version_utilities.py b/packages/python-sdk/tests/integration/test_version_utilities.py index d44bff7..9aaca06 100644 --- a/packages/python-sdk/tests/integration/test_version_utilities.py +++ b/packages/python-sdk/tests/integration/test_version_utilities.py @@ -120,15 +120,16 @@ class TestStorageEdgeCases: def test_ensure_project_file_creates_structure(self, temp_workspace: Path) -> None: ensure_project_file(temp_workspace, provider_id="unity") project = read_project(temp_workspace) - assert project["provider"]["type"] == "unity" - assert "environments" in project["provider"] - assert "dev" in project["provider"]["environments"] + scope = project.get("defaultTarget", "default") + assert project["targets"][scope]["type"] == "unity" + assert "environments" in project["targets"][scope] + assert "dev" in project["targets"][scope]["environments"] def test_ensure_project_file_idempotent(self, temp_workspace: Path) -> None: ensure_project_file(temp_workspace, provider_id="unity") ensure_project_file(temp_workspace, provider_id="unity") project = read_project(temp_workspace) - assert project["version"] == 4 + assert project["version"] == 5 def test_append_ops_adds_to_changelog(self, temp_workspace: Path) -> None: ensure_project_file(temp_workspace, provider_id="unity") diff --git a/packages/python-sdk/tests/integration/test_workflows.py b/packages/python-sdk/tests/integration/test_workflows.py index afed9a1..6071f07 100644 --- a/packages/python-sdk/tests/integration/test_workflows.py +++ b/packages/python-sdk/tests/integration/test_workflows.py @@ -35,8 +35,9 @@ def test_init_to_sql_workflow(self, temp_workspace): # Verify initialization project = read_project(temp_workspace) - assert project["version"] == 4 - assert project["provider"]["type"] == "unity" + assert project["version"] == 5 + scope = project.get("defaultTarget", "default") + assert project["targets"][scope]["type"] == "unity" # Step 2: Add operations ops = [ @@ -547,7 +548,8 @@ def test_diff_between_snapshots(self, initialized_workspace, sample_operations): project = read_project(initialized_workspace) from schemax.providers import ProviderRegistry - provider = ProviderRegistry.get(project["provider"]["type"]) + scope = project.get("defaultTarget", "default") + provider = ProviderRegistry.get(project["targets"][scope]["type"]) assert provider is not None differ = provider.get_state_differ( @@ -589,7 +591,8 @@ def test_diff_with_renames(self, initialized_workspace, sample_operations): from schemax.providers import ProviderRegistry project = read_project(initialized_workspace) - provider = ProviderRegistry.get(project["provider"]["type"]) + scope = project.get("defaultTarget", "default") + provider = ProviderRegistry.get(project["targets"][scope]["type"]) assert provider is not None differ = provider.get_state_differ( @@ -648,7 +651,8 @@ def test_diff_with_complex_changes(self, initialized_workspace, sample_operation from schemax.providers import ProviderRegistry project = read_project(initialized_workspace) - provider = ProviderRegistry.get(project["provider"]["type"]) + scope = project.get("defaultTarget", "default") + provider = ProviderRegistry.get(project["targets"][scope]["type"]) assert provider is not None differ = provider.get_state_differ( diff --git a/packages/python-sdk/tests/integration/test_workflows_e2e.py b/packages/python-sdk/tests/integration/test_workflows_e2e.py index 58c1068..813af71 100644 --- a/packages/python-sdk/tests/integration/test_workflows_e2e.py +++ b/packages/python-sdk/tests/integration/test_workflows_e2e.py @@ -168,7 +168,8 @@ def test_rollback_before_baseline_blocked_without_force( create_snapshot(initialized_workspace, "Baseline", version="v0.1.0") project = read_project(initialized_workspace) - project["provider"]["environments"]["dev"]["importBaselineSnapshot"] = "v0.1.0" + scope = project.get("defaultTarget", "default") + project["targets"][scope]["environments"]["dev"]["importBaselineSnapshot"] = "v0.1.0" write_project(initialized_workspace, project) with pytest.raises(RollbackError) as exc_info: diff --git a/packages/python-sdk/tests/unit/test_base_sql_generator.py b/packages/python-sdk/tests/unit/test_base_sql_generator.py new file mode 100644 index 0000000..bbb455b --- /dev/null +++ b/packages/python-sdk/tests/unit/test_base_sql_generator.py @@ -0,0 +1,338 @@ +""" +Tests for providers/base/sql_generator.py — covers BaseSQLGenerator internals. +""" + +from unittest.mock import Mock + +from schemax.providers.base.operations import Operation +from schemax.providers.base.sql_generator import ( + BaseSQLGenerator, + SQLGenerationResult, + StatementInfo, +) + + +def _op( + op: str = "unity.add_table", + target: str = "t1", + op_id: str = "op_1", + ts: str = "2025-01-01T00:00:00Z", + payload: dict | None = None, +) -> Operation: + return Operation( + id=op_id, + ts=ts, + provider="unity", + op=op, + target=target, + payload=payload or {}, + ) + + +class ConcreteSQLGenerator(BaseSQLGenerator): + """Concrete implementation for testing BaseSQLGenerator.""" + + def _get_target_object_id(self, operation: Operation) -> str | None: + if "column" in operation.op: + return operation.payload.get("tableId", operation.target) + return operation.target + + def _is_create_operation(self, operation: Operation) -> bool: + return "add_" in operation.op + + def _is_drop_operation(self, operation: Operation) -> bool: + return "drop_" in operation.op + + def _get_dependency_level(self, operation: Operation) -> int: + if "catalog" in operation.op: + return 0 + if "schema" in operation.op: + return 1 + if "table" in operation.op: + return 2 + if "view" in operation.op: + return 3 + return 4 + + def _generate_batched_create_sql(self, object_id, batch_info): + return f"CREATE {object_id}" + + def _generate_batched_alter_sql(self, object_id, batch_info): + return f"ALTER {object_id}" + + def generate_sql_for_operation(self, operation: Operation) -> SQLGenerationResult: + return SQLGenerationResult( + sql=f"-- {operation.op} {operation.target}", + statements=[ + StatementInfo( + sql=f"-- {operation.op} {operation.target}", + operation_ids=[operation.id], + execution_order=1, + ) + ], + ) + + def can_generate_sql(self, operation: Operation) -> bool: + return "unsupported" not in operation.op + + +# ── StatementInfo / SQLGenerationResult ──────────────────────────────── + + +class TestModels: + def test_statement_info(self): + si = StatementInfo(sql="SELECT 1", operation_ids=["op1"], execution_order=1) + assert si.sql == "SELECT 1" + + def test_sql_generation_result_defaults(self): + r = SQLGenerationResult(sql="SELECT 1") + assert r.statements == [] + assert r.warnings == [] + assert r.is_idempotent is True + + +# ── BaseSQLGenerator utilities ───────────────────────────────────────── + + +class TestBaseSQLGeneratorUtilities: + def test_escape_identifier(self): + assert BaseSQLGenerator.escape_identifier("my_table") == "`my_table`" + + def test_escape_identifier_with_backticks(self): + assert BaseSQLGenerator.escape_identifier("my`table") == "`my``table`" + + def test_escape_string(self): + assert BaseSQLGenerator.escape_string("it's") == "it''s" + + def test_build_fqn(self): + gen = ConcreteSQLGenerator(state={"catalogs": []}) + fqn = gen._build_fqn("catalog", "schema", "table") + assert fqn == "`catalog`.`schema`.`table`" + + def test_build_fqn_skips_empty(self): + gen = ConcreteSQLGenerator(state={"catalogs": []}) + fqn = gen._build_fqn("catalog", "", "table") + assert fqn == "`catalog`.`table`" + + def test_build_fqn_custom_separator(self): + gen = ConcreteSQLGenerator(state={"catalogs": []}) + fqn = gen._build_fqn("a", "b", separator="::") + assert fqn == "`a`::`b`" + + +# ── _get_object_type_from_operation ──────────────────────────────────── + + +class TestGetObjectType: + def setup_method(self): + self.gen = ConcreteSQLGenerator(state={"catalogs": []}) + + def test_catalog(self): + assert self.gen._get_object_type_from_operation(_op("unity.add_catalog")) == "catalog" + + def test_schema(self): + assert self.gen._get_object_type_from_operation(_op("unity.add_schema")) == "schema" + + def test_table(self): + assert self.gen._get_object_type_from_operation(_op("unity.add_table")) == "table" + + def test_view(self): + assert self.gen._get_object_type_from_operation(_op("unity.add_view")) == "view" + + def test_column(self): + assert self.gen._get_object_type_from_operation(_op("unity.add_column")) == "column" + + def test_constraint(self): + assert self.gen._get_object_type_from_operation(_op("unity.add_constraint")) == "constraint" + + def test_unknown(self): + assert self.gen._get_object_type_from_operation(_op("unity.add_grant")) == "unknown" + + +# ── _get_object_display_name_from_op ─────────────────────────────────── + + +class TestGetObjectDisplayName: + def setup_method(self): + self.gen = ConcreteSQLGenerator(state={"catalogs": []}) + + def test_table_with_name_in_payload(self): + op = _op("unity.drop_table", payload={"name": "my_tbl"}) + assert self.gen._get_object_display_name_from_op(op) == "table 'my_tbl'" + + def test_view(self): + op = _op("unity.drop_view", payload={"name": "my_view"}) + assert self.gen._get_object_display_name_from_op(op) == "view 'my_view'" + + def test_schema(self): + op = _op("unity.drop_schema", payload={"name": "my_sch"}) + assert self.gen._get_object_display_name_from_op(op) == "schema 'my_sch'" + + def test_catalog(self): + op = _op("unity.drop_catalog", payload={"name": "my_cat"}) + assert self.gen._get_object_display_name_from_op(op) == "catalog 'my_cat'" + + def test_fallback_to_target(self): + op = _op("unity.drop_table", target="tgt_id", payload={}) + assert self.gen._get_object_display_name_from_op(op) == "table 'tgt_id'" + + def test_unknown_op_type(self): + op = _op("unity.add_grant", payload={"name": "g1"}) + assert self.gen._get_object_display_name_from_op(op) == "object 'g1'" + + +# ── generate_sql (default) ───────────────────────────────────────────── + + +class TestGenerateSQL: + def setup_method(self): + self.gen = ConcreteSQLGenerator(state={"catalogs": []}) + + def test_empty_ops(self): + assert self.gen.generate_sql([]) == "" + + def test_single_op(self): + sql = self.gen.generate_sql([_op()]) + assert "unity.add_table" in sql + assert "Operation:" in sql + + def test_unsupported_op_skipped(self): + sql = self.gen.generate_sql([_op("unity.unsupported_op")]) + assert sql == "" + + def test_multiple_ops(self): + ops = [_op(op_id="op_1"), _op(op_id="op_2", target="t2")] + sql = self.gen.generate_sql(ops) + assert "op_1" in sql + assert "op_2" in sql + + +# ── generate_sql_with_mapping (base SQLGenerator default) ────────────── + + +class TestGenerateSQLWithMapping: + def setup_method(self): + self.gen = ConcreteSQLGenerator(state={"catalogs": []}) + + def test_default_mapping(self): + result = self.gen.generate_sql_with_mapping([_op()]) + assert isinstance(result, SQLGenerationResult) + assert len(result.statements) >= 1 + + +# ── _build_dependency_graph ──────────────────────────────────────────── + + +class TestBuildDependencyGraph: + def setup_method(self): + self.gen = ConcreteSQLGenerator(state={"catalogs": []}) + + def test_single_op(self): + graph = self.gen.build_dependency_graph([_op(target="t1")]) + assert "t1" in graph.nodes + + def test_multiple_ops_same_target(self): + ops = [ + _op("unity.add_table", target="t1", op_id="op_1"), + _op("unity.update_table_comment", target="t1", op_id="op_2"), + ] + graph = self.gen.build_dependency_graph(ops) + assert "t1" in graph.nodes + assert graph.nodes["t1"].metadata["op_count"] == 2 + + def test_no_target_id_skipped(self): + gen = ConcreteSQLGenerator(state={"catalogs": []}) + # Override to return None + gen._get_target_object_id = lambda op: None + graph = gen.build_dependency_graph([_op()]) + assert len(graph.nodes) == 0 + + def test_stores_ops_by_target_in_metadata(self): + ops = [_op(target="t1")] + graph = self.gen.build_dependency_graph(ops) + assert "t1" in graph.metadata["ops_by_target"] + + +# ── _sort_operations_by_level ────────────────────────────────────────── + + +class TestSortOperationsByLevel: + def setup_method(self): + self.gen = ConcreteSQLGenerator(state={"catalogs": []}) + + def test_sorts_catalog_before_schema_before_table(self): + ops = [ + _op("unity.add_table", op_id="t"), + _op("unity.add_catalog", op_id="c"), + _op("unity.add_schema", op_id="s"), + ] + sorted_ops = self.gen._sort_operations_by_level(ops) + assert sorted_ops[0].id == "c" + assert sorted_ops[1].id == "s" + assert sorted_ops[2].id == "t" + + +# ── generate_sql_with_dependencies ───────────────────────────────────── + + +class TestGenerateSQLWithDependencies: + def setup_method(self): + self.gen = ConcreteSQLGenerator(state={"catalogs": []}) + + def test_empty_ops(self): + result = self.gen.generate_sql_with_dependencies([]) + assert result.sql == "" + assert result.statements == [] + + def test_single_op(self): + result = self.gen.generate_sql_with_dependencies([_op()]) + assert len(result.statements) == 1 + + def test_unsupported_op_adds_warning(self): + result = self.gen.generate_sql_with_dependencies([_op("unity.unsupported_op")]) + assert any("Cannot generate" in w for w in result.warnings) + assert len(result.statements) == 0 + + def test_falls_back_on_dependency_error(self): + gen = ConcreteSQLGenerator(state={"catalogs": []}) + gen._build_dependency_graph = Mock(side_effect=RuntimeError("graph fail")) + result = gen.generate_sql_with_dependencies([_op()]) + assert any("Dependency analysis failed" in w for w in result.warnings) + assert len(result.statements) == 1 # Still generates SQL via fallback + + +# ── _detect_breaking_changes ─────────────────────────────────────────── + + +class TestDetectBreakingChanges: + def setup_method(self): + self.gen = ConcreteSQLGenerator(state={"catalogs": []}) + + def test_no_warnings_for_create_ops(self): + warnings = self.gen._detect_breaking_changes([_op("unity.add_table")]) + assert warnings == [] + + def test_no_warnings_for_drop_without_dependents(self): + ops = [_op("unity.drop_table", target="t1")] + warnings = self.gen._detect_breaking_changes(ops) + assert warnings == [] + + def test_graph_build_failure_returns_empty(self): + gen = ConcreteSQLGenerator(state={"catalogs": []}) + gen._build_dependency_graph = Mock(side_effect=RuntimeError("fail")) + warnings = gen._detect_breaking_changes([_op("unity.drop_table")], graph=None) + assert warnings == [] + + def test_skip_non_drop_ops(self): + ops = [_op("unity.add_table"), _op("unity.update_table_comment")] + warnings = self.gen._detect_breaking_changes(ops) + assert warnings == [] + + +# ── _extract_operation_dependencies (default) ────────────────────────── + + +class TestExtractOperationDependencies: + def test_default_returns_empty(self): + gen = ConcreteSQLGenerator(state={"catalogs": []}) + assert gen._extract_operation_dependencies(_op()) == [] diff --git a/packages/python-sdk/tests/unit/test_batching.py b/packages/python-sdk/tests/unit/test_batching.py index d8d075d..e3fbcfa 100644 --- a/packages/python-sdk/tests/unit/test_batching.py +++ b/packages/python-sdk/tests/unit/test_batching.py @@ -339,3 +339,63 @@ def test_get_batch_statistics(self): assert stats["average_ops_per_batch"] == 2.5 assert stats["max_ops_in_batch"] == 3 assert stats["min_ops_in_batch"] == 2 + + +class TestBatchOperationsByType: + """Test batch_operations_by_type method""" + + def test_batch_by_type_empty(self): + batcher = OperationBatcher() + result = batcher.batch_operations_by_type([], lambda op: op.target, lambda op: "default") + assert result == {} + + def test_batch_by_type_groups_correctly(self): + batcher = OperationBatcher() + ops = [ + Operation( + id="op_001", + provider="test", + ts="2025-01-01T00:00:00Z", + op="test.add_column", + target="col_001", + payload={"tableId": "table_123"}, + ), + Operation( + id="op_002", + provider="test", + ts="2025-01-01T00:01:00Z", + op="test.set_property", + target="prop_001", + payload={"tableId": "table_123"}, + ), + ] + + def get_target(op): + return op.payload.get("tableId") + + def categorize(op): + if "column" in op.op: + return "column_ops" + return "property_ops" + + batches = batcher.batch_operations_by_type(ops, get_target, categorize) + assert "table_123" in batches + assert "column_ops" in batches["table_123"] + assert "property_ops" in batches["table_123"] + assert len(batches["table_123"]["column_ops"]) == 1 + assert len(batches["table_123"]["property_ops"]) == 1 + + def test_batch_by_type_skips_no_target(self): + batcher = OperationBatcher() + ops = [ + Operation( + id="op_001", + provider="test", + ts="2025-01-01T00:00:00Z", + op="test.global_op", + target="g1", + payload={}, + ), + ] + batches = batcher.batch_operations_by_type(ops, lambda _: None, lambda _: "cat") + assert len(batches) == 0 diff --git a/packages/python-sdk/tests/unit/test_bundle_command.py b/packages/python-sdk/tests/unit/test_bundle_command.py index 8d1171f..27cf5c4 100644 --- a/packages/python-sdk/tests/unit/test_bundle_command.py +++ b/packages/python-sdk/tests/unit/test_bundle_command.py @@ -16,13 +16,16 @@ def _make_project(environments: dict | None = None) -> dict: "prod": {"topLevelName": "prod_catalog", "catalogMappings": {}}, } return { - "version": 4, + "version": 5, "name": "test_project", - "provider": { - "type": "unity", - "version": "1.0.0", - "environments": environments, + "targets": { + "default": { + "type": "unity", + "version": "1.0.0", + "environments": environments, + }, }, + "defaultTarget": "default", "settings": {"catalogMode": "multi"}, } diff --git a/packages/python-sdk/tests/unit/test_cli_commands.py b/packages/python-sdk/tests/unit/test_cli_commands.py index 01d06b0..aee0f08 100644 --- a/packages/python-sdk/tests/unit/test_cli_commands.py +++ b/packages/python-sdk/tests/unit/test_cli_commands.py @@ -167,9 +167,10 @@ def test_validate_routes_json_option(monkeypatch, temp_workspace: Path) -> None: runner = CliRunner() captured: dict[str, object] = {} - def _run(_self, *, workspace: Path, json_output: bool): + def _run(_self, *, workspace: Path, json_output: bool, scope: str | None = None): captured["workspace"] = workspace captured["json_output"] = json_output + captured["scope"] = scope return SimpleNamespace(success=True) monkeypatch.setattr("schemax.cli.ValidateService.run", _run) @@ -694,9 +695,9 @@ def test_workspace_state_json_output(monkeypatch, temp_workspace: Path) -> None: read_project=lambda *, workspace: { "name": "demo", "latestSnapshot": None, - "provider": {"type": "unity", "version": "1.0.0"}, + "targets": {"default": {"type": "unity", "version": "1.0.0"}}, }, - load_current_state=lambda *, workspace, validate=False: ( + load_current_state=lambda *, workspace, validate=False, scope=None: ( {"catalogs": [{"id": "cat_1", "name": "demo"}]}, { "version": 1, diff --git a/packages/python-sdk/tests/unit/test_cli_error_paths.py b/packages/python-sdk/tests/unit/test_cli_error_paths.py new file mode 100644 index 0000000..c4af875 --- /dev/null +++ b/packages/python-sdk/tests/unit/test_cli_error_paths.py @@ -0,0 +1,870 @@ +"""CLI error-path and JSON-envelope tests for coverage gaps.""" + +import json +from pathlib import Path +from types import SimpleNamespace + +from click.testing import CliRunner + +from schemax.cli import cli + +# ── SQL command error paths ──────────────────────────────────────────── + + +def test_sql_json_error_on_workflow_validation_error(monkeypatch, temp_workspace: Path) -> None: + from schemax.domain.errors import WorkflowValidationError + + def _raise(_self, **kwargs): + raise WorkflowValidationError( + code="LEGACY_SINGLE_CATALOG_UNSUPPORTED", + message="Legacy workspace not supported", + ) + + monkeypatch.setattr("schemax.cli.SqlService.run", _raise) + runner = CliRunner() + result = runner.invoke(cli, ["sql", "--json", str(temp_workspace)]) + assert result.exit_code == 1 + payload = json.loads(result.output) + assert payload["status"] == "error" + assert payload["errors"][0]["code"] == "LEGACY_SINGLE_CATALOG_UNSUPPORTED" + + +def test_sql_json_unexpected_error(monkeypatch, temp_workspace: Path) -> None: + def _raise(_self, **kwargs): + raise RuntimeError("unexpected boom") + + monkeypatch.setattr("schemax.cli.SqlService.run", _raise) + runner = CliRunner() + result = runner.invoke(cli, ["sql", "--json", str(temp_workspace)]) + assert result.exit_code == 1 + payload = json.loads(result.output) + assert payload["errors"][0]["code"] == "UNEXPECTED_ERROR" + + +def test_sql_non_json_unexpected_error(monkeypatch, temp_workspace: Path) -> None: + def _raise(_self, **kwargs): + raise RuntimeError("unexpected boom") + + monkeypatch.setattr("schemax.cli.SqlService.run", _raise) + runner = CliRunner() + result = runner.invoke(cli, ["sql", str(temp_workspace)]) + assert result.exit_code == 1 + assert "Unexpected error" in result.output + + +# ── Validate command error paths ─────────────────────────────────────── + + +def test_validate_json_on_command_validation_error(monkeypatch, temp_workspace: Path) -> None: + from schemax.commands import ValidationError as CommandValidationError + + def _raise(_self, **kwargs): + raise CommandValidationError("bad project") + + monkeypatch.setattr("schemax.cli.ValidateService.run", _raise) + runner = CliRunner() + result = runner.invoke(cli, ["validate", "--json", str(temp_workspace)]) + assert result.exit_code == 1 + payload = json.loads(result.output) + assert payload["errors"][0]["code"] == "VALIDATION_FAILED" + + +def test_validate_non_json_command_validation_error(monkeypatch, temp_workspace: Path) -> None: + from schemax.commands import ValidationError as CommandValidationError + + def _raise(_self, **kwargs): + raise CommandValidationError("bad project") + + monkeypatch.setattr("schemax.cli.ValidateService.run", _raise) + runner = CliRunner() + result = runner.invoke(cli, ["validate", str(temp_workspace)]) + assert result.exit_code == 1 + assert "Validation failed" in result.output + + +def test_validate_json_workflow_validation_error(monkeypatch, temp_workspace: Path) -> None: + from schemax.domain.errors import WorkflowValidationError + + def _raise(_self, **kwargs): + raise WorkflowValidationError(code="LEGACY", message="legacy workspace") + + monkeypatch.setattr("schemax.cli.ValidateService.run", _raise) + runner = CliRunner() + result = runner.invoke(cli, ["validate", "--json", str(temp_workspace)]) + assert result.exit_code == 1 + payload = json.loads(result.output) + assert payload["errors"][0]["code"] == "LEGACY" + + +def test_validate_json_unexpected_error(monkeypatch, temp_workspace: Path) -> None: + def _raise(_self, **kwargs): + raise RuntimeError("kaboom") + + monkeypatch.setattr("schemax.cli.ValidateService.run", _raise) + runner = CliRunner() + result = runner.invoke(cli, ["validate", "--json", str(temp_workspace)]) + assert result.exit_code == 1 + payload = json.loads(result.output) + assert payload["errors"][0]["code"] == "UNEXPECTED_ERROR" + + +def test_validate_non_json_unexpected_error(monkeypatch, temp_workspace: Path) -> None: + def _raise(_self, **kwargs): + raise RuntimeError("kaboom") + + monkeypatch.setattr("schemax.cli.ValidateService.run", _raise) + runner = CliRunner() + result = runner.invoke(cli, ["validate", str(temp_workspace)]) + assert result.exit_code == 1 + assert "Unexpected error" in result.output + + +# ── Diff command error paths ─────────────────────────────────────────── + + +def test_diff_json_error_on_diff_error(monkeypatch, temp_workspace: Path) -> None: + from schemax.commands import DiffError + + def _raise(_self, **kwargs): + raise DiffError("diff failed") + + monkeypatch.setattr("schemax.cli.DiffService.run", _raise) + runner = CliRunner() + result = runner.invoke( + cli, ["diff", "--from", "v0.1.0", "--to", "v0.2.0", "--json", str(temp_workspace)] + ) + assert result.exit_code == 1 + payload = json.loads(result.output) + assert payload["errors"][0]["code"] == "DIFF_FAILED" + + +def test_diff_non_json_diff_error(monkeypatch, temp_workspace: Path) -> None: + from schemax.commands import DiffError + + def _raise(_self, **kwargs): + raise DiffError("diff failed") + + monkeypatch.setattr("schemax.cli.DiffService.run", _raise) + runner = CliRunner() + result = runner.invoke(cli, ["diff", "--from", "v0.1.0", "--to", "v0.2.0", str(temp_workspace)]) + assert result.exit_code == 1 + assert "Diff generation failed" in result.output + + +def test_diff_json_workflow_validation_error(monkeypatch, temp_workspace: Path) -> None: + from schemax.domain.errors import WorkflowValidationError + + def _raise(_self, **kwargs): + raise WorkflowValidationError(code="LEGACY", message="legacy workspace") + + monkeypatch.setattr("schemax.cli.DiffService.run", _raise) + runner = CliRunner() + result = runner.invoke( + cli, ["diff", "--from", "v0.1.0", "--to", "v0.2.0", "--json", str(temp_workspace)] + ) + assert result.exit_code == 1 + payload = json.loads(result.output) + assert payload["errors"][0]["code"] == "LEGACY" + + +def test_diff_json_unexpected_error(monkeypatch, temp_workspace: Path) -> None: + def _raise(_self, **kwargs): + raise RuntimeError("boom") + + monkeypatch.setattr("schemax.cli.DiffService.run", _raise) + runner = CliRunner() + result = runner.invoke( + cli, ["diff", "--from", "v0.1.0", "--to", "v0.2.0", "--json", str(temp_workspace)] + ) + assert result.exit_code == 1 + payload = json.loads(result.output) + assert payload["errors"][0]["code"] == "UNEXPECTED_ERROR" + + +def test_diff_non_json_unexpected_error(monkeypatch, temp_workspace: Path) -> None: + def _raise(_self, **kwargs): + raise RuntimeError("boom") + + monkeypatch.setattr("schemax.cli.DiffService.run", _raise) + runner = CliRunner() + result = runner.invoke(cli, ["diff", "--from", "v0.1.0", "--to", "v0.2.0", str(temp_workspace)]) + assert result.exit_code == 1 + assert "Unexpected error" in result.output + + +# ── Bundle error paths ───────────────────────────────────────────────── + + +def test_bundle_json_failure(monkeypatch, tmp_path: Path) -> None: + from schemax.core.storage import ensure_project_file + + ensure_project_file(tmp_path, provider_id="unity") + monkeypatch.chdir(tmp_path) + + monkeypatch.setattr( + "schemax.cli.BundleService.run", + lambda _self, **kwargs: SimpleNamespace( + success=False, message="bundle generation failed", data={} + ), + ) + runner = CliRunner() + result = runner.invoke(cli, ["bundle", "--json"]) + assert result.exit_code == 1 + payload = json.loads(result.output) + assert payload["errors"][0]["code"] == "BUNDLE_FAILED" + + +def test_bundle_json_exception(monkeypatch, tmp_path: Path) -> None: + from schemax.core.storage import ensure_project_file + + ensure_project_file(tmp_path, provider_id="unity") + monkeypatch.chdir(tmp_path) + + def _raise(_self, **kwargs): + raise RuntimeError("bundle crash") + + monkeypatch.setattr("schemax.cli.BundleService.run", _raise) + runner = CliRunner() + result = runner.invoke(cli, ["bundle", "--json"]) + assert result.exit_code == 1 + payload = json.loads(result.output) + assert payload["errors"][0]["code"] == "BUNDLE_ERROR" + + +def test_bundle_non_json_failure(monkeypatch, tmp_path: Path) -> None: + from schemax.core.storage import ensure_project_file + + ensure_project_file(tmp_path, provider_id="unity") + monkeypatch.chdir(tmp_path) + + monkeypatch.setattr( + "schemax.cli.BundleService.run", + lambda _self, **kwargs: SimpleNamespace( + success=False, message="bundle generation failed", data={} + ), + ) + runner = CliRunner() + result = runner.invoke(cli, ["bundle"]) + assert result.exit_code == 1 + assert "bundle generation failed" in result.output + + +# ── Rollback JSON paths ─────────────────────────────────────────────── + + +def test_rollback_json_partial_success(monkeypatch, temp_workspace: Path) -> None: + monkeypatch.setattr( + "schemax.cli.RollbackService.run_partial", + lambda _self, **kwargs: SimpleNamespace( + data={ + "result": SimpleNamespace( + success=True, operations_rolled_back=3, error_message=None + ) + } + ), + ) + runner = CliRunner() + result = runner.invoke( + cli, + [ + "rollback", + "--partial", + "--deployment", + "d1", + "--target", + "dev", + "--profile", + "p", + "--warehouse-id", + "wh", + "--json", + str(temp_workspace), + ], + ) + assert result.exit_code == 0 + payload = json.loads(result.output) + assert payload["status"] == "success" + assert payload["data"]["result"]["operations_rolled_back"] == 3 + + +def test_rollback_json_partial_failure(monkeypatch, temp_workspace: Path) -> None: + monkeypatch.setattr( + "schemax.cli.RollbackService.run_partial", + lambda _self, **kwargs: SimpleNamespace( + data={ + "result": SimpleNamespace( + success=False, operations_rolled_back=0, error_message="auto blocked" + ) + } + ), + ) + runner = CliRunner() + result = runner.invoke( + cli, + [ + "rollback", + "--partial", + "--deployment", + "d1", + "--target", + "dev", + "--profile", + "p", + "--warehouse-id", + "wh", + "--json", + str(temp_workspace), + ], + ) + assert result.exit_code == 1 + payload = json.loads(result.output) + assert payload["status"] == "error" + assert payload["errors"][0]["code"] == "ROLLBACK_FAILED" + + +def test_rollback_json_partial_missing_result(monkeypatch, temp_workspace: Path) -> None: + monkeypatch.setattr( + "schemax.cli.RollbackService.run_partial", + lambda _self, **kwargs: SimpleNamespace(data=None), + ) + runner = CliRunner() + result = runner.invoke( + cli, + [ + "rollback", + "--partial", + "--deployment", + "d1", + "--target", + "dev", + "--profile", + "p", + "--warehouse-id", + "wh", + "--json", + str(temp_workspace), + ], + ) + assert result.exit_code == 1 + payload = json.loads(result.output) + assert "missing rollback result" in payload["errors"][0]["message"] + + +def test_rollback_json_complete_missing_target(temp_workspace: Path) -> None: + runner = CliRunner() + result = runner.invoke( + cli, + [ + "rollback", + "--to-snapshot", + "v0.1.0", + "--profile", + "p", + "--warehouse-id", + "wh", + "--json", + str(temp_workspace), + ], + ) + assert result.exit_code == 1 + payload = json.loads(result.output) + assert payload["errors"][0]["code"] == "ROLLBACK_INVALID_ARGS" + assert "target" in payload["errors"][0]["message"].lower() + + +def test_rollback_json_complete_missing_warehouse(temp_workspace: Path) -> None: + runner = CliRunner() + result = runner.invoke( + cli, + [ + "rollback", + "--to-snapshot", + "v0.1.0", + "--target", + "dev", + "--profile", + "p", + "--json", + str(temp_workspace), + ], + ) + assert result.exit_code == 1 + payload = json.loads(result.output) + assert payload["errors"][0]["code"] == "ROLLBACK_INVALID_ARGS" + assert "warehouse" in payload["errors"][0]["message"].lower() + + +def test_rollback_non_json_no_mode_shows_usage(temp_workspace: Path) -> None: + runner = CliRunner() + result = runner.invoke( + cli, + [ + "rollback", + "--target", + "dev", + "--profile", + "p", + "--warehouse-id", + "wh", + str(temp_workspace), + ], + ) + assert result.exit_code == 1 + assert "Must specify" in result.output + + +def test_rollback_json_unexpected_error(monkeypatch, temp_workspace: Path) -> None: + def _raise(**kwargs): + raise RuntimeError("unexpected") + + monkeypatch.setattr("schemax.cli._handle_rollback_dispatch", _raise) + runner = CliRunner() + result = runner.invoke( + cli, + [ + "rollback", + "--partial", + "--deployment", + "d1", + "--target", + "dev", + "--profile", + "p", + "--warehouse-id", + "wh", + "--json", + str(temp_workspace), + ], + ) + assert result.exit_code == 1 + payload = json.loads(result.output) + assert payload["errors"][0]["code"] == "UNEXPECTED_ERROR" + + +def test_rollback_non_json_unexpected_error(monkeypatch, temp_workspace: Path) -> None: + def _raise(**kwargs): + raise RuntimeError("unexpected crash") + + monkeypatch.setattr("schemax.cli._handle_rollback_dispatch", _raise) + runner = CliRunner() + result = runner.invoke( + cli, + [ + "rollback", + "--partial", + "--deployment", + "d1", + "--target", + "dev", + "--profile", + "p", + "--warehouse-id", + "wh", + str(temp_workspace), + ], + ) + assert result.exit_code == 1 + assert "Rollback error" in result.output + + +# ── Rollback non-JSON dispatch paths ────────────────────────────────── + + +def test_rollback_non_json_partial_success(monkeypatch, temp_workspace: Path) -> None: + monkeypatch.setattr( + "schemax.cli.RollbackService.run_partial", + lambda _self, **kwargs: SimpleNamespace( + data={ + "result": SimpleNamespace( + success=True, operations_rolled_back=2, error_message=None + ) + } + ), + ) + runner = CliRunner() + result = runner.invoke( + cli, + [ + "rollback", + "--partial", + "--deployment", + "d1", + "--target", + "dev", + "--profile", + "p", + "--warehouse-id", + "wh", + str(temp_workspace), + ], + ) + assert result.exit_code == 0 + assert "Rolled back 2 operations" in result.output + + +def test_rollback_non_json_partial_failure(monkeypatch, temp_workspace: Path) -> None: + monkeypatch.setattr( + "schemax.cli.RollbackService.run_partial", + lambda _self, **kwargs: SimpleNamespace( + data={ + "result": SimpleNamespace( + success=False, operations_rolled_back=0, error_message="blocked" + ) + } + ), + ) + runner = CliRunner() + result = runner.invoke( + cli, + [ + "rollback", + "--partial", + "--deployment", + "d1", + "--target", + "dev", + "--profile", + "p", + "--warehouse-id", + "wh", + str(temp_workspace), + ], + ) + assert result.exit_code == 1 + assert "Rollback failed" in result.output + + +def test_rollback_non_json_partial_missing_result(monkeypatch, temp_workspace: Path) -> None: + monkeypatch.setattr( + "schemax.cli.RollbackService.run_partial", + lambda _self, **kwargs: SimpleNamespace(data=None), + ) + runner = CliRunner() + result = runner.invoke( + cli, + [ + "rollback", + "--partial", + "--deployment", + "d1", + "--target", + "dev", + "--profile", + "p", + "--warehouse-id", + "wh", + str(temp_workspace), + ], + ) + assert result.exit_code == 1 + assert "missing rollback result" in result.output + + +def test_rollback_non_json_complete_missing_target(temp_workspace: Path) -> None: + runner = CliRunner() + result = runner.invoke( + cli, + [ + "rollback", + "--to-snapshot", + "v0.1.0", + "--profile", + "p", + "--warehouse-id", + "wh", + str(temp_workspace), + ], + ) + assert result.exit_code == 1 + assert "--target required" in result.output + + +def test_rollback_non_json_complete_missing_warehouse(temp_workspace: Path) -> None: + runner = CliRunner() + result = runner.invoke( + cli, + [ + "rollback", + "--to-snapshot", + "v0.1.0", + "--target", + "dev", + "--profile", + "p", + str(temp_workspace), + ], + ) + assert result.exit_code == 1 + assert "--warehouse-id required" in result.output + + +# ── Snapshot command error paths ────────────────────────────────────── + + +def test_snapshot_validate_non_json_all_good(monkeypatch, temp_workspace: Path) -> None: + monkeypatch.setattr( + "schemax.cli.SnapshotService.validate", + lambda _self, **kwargs: SimpleNamespace(success=True, data={"stale_snapshots": []}), + ) + runner = CliRunner() + result = runner.invoke(cli, ["snapshot", "validate", str(temp_workspace)]) + assert result.exit_code == 0 + assert "up to date" in result.output + + +def test_snapshot_validate_non_json_stale(monkeypatch, temp_workspace: Path) -> None: + monkeypatch.setattr( + "schemax.cli.SnapshotService.validate", + lambda _self, **kwargs: SimpleNamespace( + success=True, + data={ + "stale_snapshots": [ + { + "version": "v0.4.0", + "currentBase": "v0.3.0", + "shouldBeBase": "v0.3.1", + "missing": ["v0.3.1"], + } + ] + }, + ), + ) + runner = CliRunner() + result = runner.invoke(cli, ["snapshot", "validate", str(temp_workspace)]) + assert result.exit_code == 1 + assert "stale" in result.output + assert "v0.4.0" in result.output + assert "schemax snapshot rebase" in result.output + + +def test_snapshot_rebase_conflict(monkeypatch, temp_workspace: Path) -> None: + monkeypatch.setattr( + "schemax.cli.SnapshotService.rebase", + lambda _self, **kwargs: SimpleNamespace( + success=False, + data={ + "result": SimpleNamespace( + success=False, applied_count=1, conflict_count=2, conflict_log_path="/tmp/log" + ) + }, + ), + ) + runner = CliRunner() + result = runner.invoke(cli, ["snapshot", "rebase", "v0.4.0", str(temp_workspace)]) + assert result.exit_code == 1 + assert "conflicts" in result.output + + +def test_snapshot_rebase_rebase_error(monkeypatch, temp_workspace: Path) -> None: + from schemax.commands.snapshot_rebase import RebaseError + + monkeypatch.setattr( + "schemax.cli.SnapshotService.rebase", + lambda _self, **kwargs: (_ for _ in ()).throw(RebaseError("no base")), + ) + runner = CliRunner() + result = runner.invoke(cli, ["snapshot", "rebase", "v0.4.0", str(temp_workspace)]) + assert result.exit_code == 1 + assert "Rebase failed" in result.output + + +def test_snapshot_rebase_unexpected_error(monkeypatch, temp_workspace: Path) -> None: + monkeypatch.setattr( + "schemax.cli.SnapshotService.rebase", + lambda _self, **kwargs: (_ for _ in ()).throw(RuntimeError("disk error")), + ) + runner = CliRunner() + result = runner.invoke(cli, ["snapshot", "rebase", "v0.4.0", str(temp_workspace)]) + assert result.exit_code == 1 + assert "Unexpected error" in result.output + + +# ── Snapshot create paths ───────────────────────────────────────────── + + +def test_snapshot_create_with_ops(monkeypatch, temp_workspace: Path) -> None: + monkeypatch.setattr( + "schemax.cli.workspace_repo", + SimpleNamespace(read_changelog=lambda *, workspace: {"ops": [{"id": "op_1"}]}), + ) + monkeypatch.setattr( + "schemax.cli.SnapshotService.create", + lambda _self, **kwargs: SimpleNamespace( + success=True, + data={ + "project": {"snapshots": [{"version": "v0.1.0"}]}, + "snapshot": { + "version": "v0.1.0", + "name": "test", + "comment": "a comment", + "tags": ["t1"], + "operations": [{"id": "op_1"}], + }, + }, + ), + ) + runner = CliRunner() + result = runner.invoke( + cli, + [ + "snapshot", + "create", + "--name", + "test", + "--comment", + "a comment", + "--tags", + "t1", + str(temp_workspace), + ], + ) + assert result.exit_code == 0 + assert "Snapshot created" in result.output + assert "v0.1.0" in result.output + assert "a comment" in result.output + assert "t1" in result.output + + +def test_snapshot_create_file_not_found(monkeypatch, temp_workspace: Path) -> None: + monkeypatch.setattr( + "schemax.cli.workspace_repo", + SimpleNamespace( + read_changelog=lambda *, workspace: (_ for _ in ()).throw( + FileNotFoundError("no project") + ) + ), + ) + runner = CliRunner() + result = runner.invoke(cli, ["snapshot", "create", "--name", "test", str(temp_workspace)]) + assert result.exit_code == 1 + assert "SchemaX project" in result.output + + +# ── Workspace state paths ───────────────────────────────────────────── + + +def test_workspace_state_non_json(monkeypatch, temp_workspace: Path) -> None: + fake_provider = SimpleNamespace( + info=SimpleNamespace(id="unity", name="Unity Catalog", version="1.0.0"), + capabilities=SimpleNamespace( + model_dump=lambda: {}, + ), + ) + fake_repo = SimpleNamespace( + read_project=lambda *, workspace: {"name": "demo", "latestSnapshot": None, "targets": {}}, + load_current_state=lambda *, workspace, validate=False, scope=None: ( + {"catalogs": []}, + {"ops": [], "version": 1}, + fake_provider, + None, + ), + ) + monkeypatch.setattr("schemax.cli.workspace_repo", fake_repo) + runner = CliRunner() + result = runner.invoke(cli, ["workspace-state", str(temp_workspace)]) + assert result.exit_code == 0 + assert "Loaded workspace state" in result.output + + +def test_workspace_state_state_only_mode(monkeypatch, temp_workspace: Path) -> None: + fake_provider = SimpleNamespace( + info=SimpleNamespace(id="unity", name="Unity Catalog", version="1.0.0"), + capabilities=SimpleNamespace( + model_dump=lambda: {}, + supported_operations=[], + supported_object_types=[], + features={}, + hierarchy=None, + ), + ) + fake_repo = SimpleNamespace( + read_project=lambda *, workspace: {"name": "demo", "latestSnapshot": None, "targets": {}}, + load_current_state=lambda *, workspace, validate=False, scope=None: ( + {"catalogs": []}, + {"ops": [{"id": "op_1"}], "version": 1}, + fake_provider, + None, + ), + ) + monkeypatch.setattr("schemax.cli.workspace_repo", fake_repo) + runner = CliRunner() + result = runner.invoke( + cli, ["workspace-state", "--json", "--payload-mode", "state-only", str(temp_workspace)] + ) + assert result.exit_code == 0 + payload = json.loads(result.output) + assert payload["data"]["changelog"]["ops"] == [] + assert payload["data"]["changelog"]["opsCount"] == 1 + + +# ── Serialize helpers ───────────────────────────────────────────────── + + +def test_serialize_operation_mapping() -> None: + from schemax.cli import _serialize_operation + + result = _serialize_operation({"id": "op_1", "op": "unity.add_catalog"}) + assert result == {"id": "op_1", "op": "unity.add_catalog"} + + +def test_serialize_operation_model() -> None: + from schemax.cli import _serialize_operation + from schemax.providers.base.operations import Operation + + op = Operation( + id="op_1", + ts="2025-01-01T00:00:00Z", + provider="unity", + op="unity.add_catalog", + target="c1", + payload={}, + ) + result = _serialize_operation(op) + assert result["id"] == "op_1" + + +def test_serialize_operation_unsupported() -> None: + import pytest + + from schemax.cli import _serialize_operation + + with pytest.raises(TypeError, match="Unsupported"): + _serialize_operation("not a valid operation") + + +def test_serialize_provider_capabilities_with_hierarchy() -> None: + from schemax.cli import _serialize_provider_capabilities + from schemax.providers.base.hierarchy import Hierarchy, HierarchyLevel + + levels = [ + HierarchyLevel( + name="catalog", display_name="Catalog", plural_name="catalogs", is_container=True + ), + HierarchyLevel( + name="table", display_name="Table", plural_name="tables", is_container=False + ), + ] + hierarchy = Hierarchy(levels) + caps = SimpleNamespace( + supported_operations=["add_catalog"], + supported_object_types=["catalog"], + features={"views": True}, + hierarchy=hierarchy, + ) + result = _serialize_provider_capabilities(caps) + assert len(result["hierarchy"]["levels"]) == 2 + assert result["hierarchy"]["levels"][0]["name"] == "catalog" + assert result["features"] == {"views": True} + + +def test_serialize_provider_capabilities_no_hierarchy() -> None: + from schemax.cli import _serialize_provider_capabilities + + caps = SimpleNamespace( + supported_operations=[], + supported_object_types=[], + features={}, + hierarchy=None, + ) + result = _serialize_provider_capabilities(caps) + assert result["hierarchy"]["levels"] == [] diff --git a/packages/python-sdk/tests/unit/test_diff_command.py b/packages/python-sdk/tests/unit/test_diff_command.py index 330d4f1..686e817 100644 --- a/packages/python-sdk/tests/unit/test_diff_command.py +++ b/packages/python-sdk/tests/unit/test_diff_command.py @@ -65,7 +65,7 @@ def test_generate_diff_raises_when_provider_not_found(monkeypatch: pytest.Monkey "v0.1.0": {"state": {"catalogs": []}}, "v0.2.0": {"state": {"catalogs": []}}, }, - project={"provider": {"type": "missing"}}, + project={"targets": {"default": {"type": "missing"}}, "defaultTarget": "default"}, ) with pytest.raises(DiffError, match="Provider 'missing' not found"): @@ -96,7 +96,8 @@ def test_generate_diff_with_sql_and_target_mapping(monkeypatch: pytest.MonkeyPat "v0.2.0": {"state": {"catalogs": [{"name": "demo"}]}, "operations": []}, }, project={ - "provider": {"type": "unity"}, + "targets": {"default": {"type": "unity"}}, + "defaultTarget": "default", "environments": {"dev": {"topLevelName": "dev_demo"}}, }, env_config={"topLevelName": "dev_demo", "catalogMappings": {"demo": "dev_demo"}}, @@ -119,7 +120,7 @@ def test_generate_diff_with_sql_and_target_mapping(monkeypatch: pytest.MonkeyPat def test_generate_diff_missing_snapshot_error_has_context(monkeypatch: pytest.MonkeyPatch) -> None: repo = _RepoStub( snapshots={"v0.2.0": {"state": {"catalogs": []}, "operations": []}}, - project={"provider": {"type": "unity"}}, + project={"targets": {"default": {"type": "unity"}}, "defaultTarget": "default"}, raise_on_version="v0.1.0", ) diff --git a/packages/python-sdk/tests/unit/test_exceptions.py b/packages/python-sdk/tests/unit/test_exceptions.py new file mode 100644 index 0000000..f43b53f --- /dev/null +++ b/packages/python-sdk/tests/unit/test_exceptions.py @@ -0,0 +1,32 @@ +"""Tests for providers/base/exceptions.py.""" + +from schemax.providers.base.exceptions import ( + CircularDependencyError, + DependencyValidationError, + MissingDependencyError, + SchemaXProviderError, +) + + +class TestExceptions: + def test_base_error(self): + err = SchemaXProviderError("base error") + assert str(err) == "base error" + + def test_circular_dependency_error(self): + err = CircularDependencyError([["a", "b", "a"], ["c", "d", "c"]]) + assert err.cycles == [["a", "b", "a"], ["c", "d", "c"]] + assert "a → b → a" in str(err) + assert "c → d → c" in str(err) + assert "Circular dependencies" in str(err) + + def test_dependency_validation_error(self): + err = DependencyValidationError("validation failed") + assert isinstance(err, SchemaXProviderError) + + def test_missing_dependency_error(self): + err = MissingDependencyError("my_view", "missing_table") + assert err.object_name == "my_view" + assert err.missing_dependency == "missing_table" + assert "my_view" in str(err) + assert "missing_table" in str(err) diff --git a/packages/python-sdk/tests/unit/test_hierarchy_classes.py b/packages/python-sdk/tests/unit/test_hierarchy_classes.py new file mode 100644 index 0000000..b3e4514 --- /dev/null +++ b/packages/python-sdk/tests/unit/test_hierarchy_classes.py @@ -0,0 +1,81 @@ +""" +Tests for providers/base/hierarchy.py — covers all branches. +""" + +import pytest + +from schemax.providers.base.hierarchy import Hierarchy, HierarchyLevel + + +def _level(name: str, *, container: bool = False, icon: str | None = None) -> HierarchyLevel: + return HierarchyLevel( + name=name, + display_name=name.capitalize(), + plural_name=f"{name}s", + icon=icon, + is_container=container, + ) + + +class TestHierarchyLevel: + def test_basic_fields(self): + level = _level("table") + assert level.name == "table" + assert level.display_name == "Table" + assert level.plural_name == "tables" + assert level.icon is None + assert level.is_container is False + + def test_container_with_icon(self): + level = _level("catalog", container=True, icon="folder") + assert level.is_container is True + assert level.icon == "folder" + + +class TestHierarchy: + def test_empty_raises_value_error(self): + with pytest.raises(ValueError, match="at least one level"): + Hierarchy([]) + + def test_get_depth(self): + h = Hierarchy([_level("catalog"), _level("schema"), _level("table")]) + assert h.get_depth() == 3 + + def test_get_level_valid(self): + h = Hierarchy([_level("catalog"), _level("schema"), _level("table")]) + assert h.get_level(0).name == "catalog" + assert h.get_level(1).name == "schema" + assert h.get_level(2).name == "table" + + def test_get_level_out_of_range(self): + h = Hierarchy([_level("catalog")]) + assert h.get_level(-1) is None + assert h.get_level(1) is None + assert h.get_level(99) is None + + def test_get_level_by_name(self): + h = Hierarchy([_level("catalog"), _level("schema"), _level("table")]) + level = h.get_level_by_name("schema") + assert level is not None + assert level.name == "schema" + + def test_get_level_by_name_not_found(self): + h = Hierarchy([_level("catalog")]) + assert h.get_level_by_name("nonexistent") is None + + def test_get_level_depth(self): + h = Hierarchy([_level("catalog"), _level("schema"), _level("table")]) + assert h.get_level_depth("catalog") == 0 + assert h.get_level_depth("schema") == 1 + assert h.get_level_depth("table") == 2 + + def test_get_level_depth_not_found(self): + h = Hierarchy([_level("catalog")]) + assert h.get_level_depth("missing") == -1 + + def test_single_level(self): + h = Hierarchy([_level("database")]) + assert h.get_depth() == 1 + assert h.get_level(0).name == "database" + assert h.get_level_by_name("database") is not None + assert h.get_level_depth("database") == 0 diff --git a/packages/python-sdk/tests/unit/test_rollback_extended.py b/packages/python-sdk/tests/unit/test_rollback_extended.py new file mode 100644 index 0000000..7903c11 --- /dev/null +++ b/packages/python-sdk/tests/unit/test_rollback_extended.py @@ -0,0 +1,371 @@ +""" +Extended tests for rollback.py — covers internal functions for coverage. +""" + +from pathlib import Path +from unittest.mock import Mock, patch + +import pytest + +from schemax.commands.rollback import ( + RollbackError, + RollbackResult, + _build_pre_deployment_state, + _check_already_at_target_version, + _classify_complete_rollback_safety, + _enforce_import_baseline_guard, + rollback_complete, +) +from schemax.providers.base.operations import Operation +from schemax.providers.base.reverse_generator import SafetyLevel, SafetyReport + +# ── _enforce_import_baseline_guard ───────────────────────────────────── + + +class TestEnforceImportBaselineGuard: + def test_no_baseline_noop(self): + _enforce_import_baseline_guard({}, "v0.1.0", force=False, no_interaction=False) + + def test_target_after_baseline_noop(self): + env_config = {"importBaselineSnapshot": "v0.1.0"} + _enforce_import_baseline_guard(env_config, "v0.2.0", force=False, no_interaction=False) + + def test_target_equal_baseline_noop(self): + env_config = {"importBaselineSnapshot": "v0.1.0"} + _enforce_import_baseline_guard(env_config, "v0.1.0", force=False, no_interaction=False) + + def test_target_before_baseline_raises_without_force(self): + env_config = {"importBaselineSnapshot": "v0.2.0"} + with pytest.raises(RollbackError, match="before the import baseline"): + _enforce_import_baseline_guard(env_config, "v0.1.0", force=False, no_interaction=False) + + def test_target_before_baseline_force_no_interaction(self): + env_config = {"importBaselineSnapshot": "v0.2.0"} + # force=True + no_interaction=True → should pass + _enforce_import_baseline_guard(env_config, "v0.1.0", force=True, no_interaction=True) + + def test_invalid_version_strings_noop(self): + env_config = {"importBaselineSnapshot": "not-a-version"} + # Invalid versions → ValueError caught → noop + _enforce_import_baseline_guard( + env_config, "also-invalid", force=False, no_interaction=False + ) + + @patch("schemax.commands.rollback.Confirm.ask", return_value=False) + def test_force_user_cancels(self, mock_ask): + env_config = {"importBaselineSnapshot": "v0.2.0"} + with pytest.raises(RollbackError, match="cancelled by user"): + _enforce_import_baseline_guard(env_config, "v0.1.0", force=True, no_interaction=False) + + +# ── _check_already_at_target_version ─────────────────────────────────── + + +class TestCheckAlreadyAtTargetVersion: + def test_returns_none_when_db_query_fails(self): + tracker = Mock() + tracker.get_latest_deployment.side_effect = RuntimeError("no connection") + result = _check_already_at_target_version(tracker, "dev", "v0.1.0") + assert result is None + + def test_returns_none_when_no_deployment(self): + tracker = Mock() + tracker.get_latest_deployment.return_value = None + result = _check_already_at_target_version(tracker, "dev", "v0.1.0") + assert result is None + + def test_returns_none_when_version_differs(self): + tracker = Mock() + tracker.get_latest_deployment.return_value = {"version": "v0.2.0"} + result = _check_already_at_target_version(tracker, "dev", "v0.1.0") + assert result is None + + def test_returns_success_when_already_at_target(self): + tracker = Mock() + tracker.get_latest_deployment.return_value = {"version": "v0.1.0"} + result = _check_already_at_target_version(tracker, "dev", "v0.1.0") + assert result is not None + assert result.success is True + assert result.operations_rolled_back == 0 + + +# ── _classify_complete_rollback_safety ───────────────────────────────── + + +class TestClassifyCompleteRollbackSafety: + def _make_op(self, op_type: str = "unity.drop_table") -> Operation: + return Operation( + id="op_1", + ts="2025-01-01T00:00:00Z", + provider="unity", + op=op_type, + target="t1", + payload={}, + ) + + def test_safe_operations(self): + validator = Mock() + validator.validate.return_value = SafetyReport( + level=SafetyLevel.SAFE, reason="safe", data_at_risk=0 + ) + safe, risky, destructive = _classify_complete_rollback_safety( + [self._make_op()], validator, None + ) + assert len(safe) == 1 + assert len(risky) == 0 + assert len(destructive) == 0 + + def test_risky_operations(self): + validator = Mock() + validator.validate.return_value = SafetyReport( + level=SafetyLevel.RISKY, reason="risky", data_at_risk=0 + ) + safe, risky, destructive = _classify_complete_rollback_safety( + [self._make_op()], validator, None + ) + assert len(safe) == 0 + assert len(risky) == 1 + + def test_destructive_operations(self): + validator = Mock() + validator.validate.return_value = SafetyReport( + level=SafetyLevel.DESTRUCTIVE, reason="destructive", data_at_risk=100 + ) + safe, risky, destructive = _classify_complete_rollback_safety( + [self._make_op()], validator, None + ) + assert len(destructive) == 1 + + def test_validation_error_classified_as_risky(self): + validator = Mock() + validator.validate.side_effect = RuntimeError("unexpected") + safe, risky, destructive = _classify_complete_rollback_safety( + [self._make_op()], validator, None + ) + assert len(risky) == 1 + assert "Validation failed" in risky[0][1].reason + + def test_mixed_operations(self): + validator = Mock() + reports = [ + SafetyReport(level=SafetyLevel.SAFE, reason="safe", data_at_risk=0), + SafetyReport(level=SafetyLevel.RISKY, reason="risky", data_at_risk=0), + SafetyReport(level=SafetyLevel.DESTRUCTIVE, reason="destructive", data_at_risk=10), + ] + validator.validate.side_effect = reports + ops = [self._make_op() for _ in range(3)] + # Fix unique IDs + ops[1] = Operation( + id="op_2", + ts="2025-01-01T00:00:00Z", + provider="unity", + op="unity.drop_schema", + target="s1", + payload={}, + ) + ops[2] = Operation( + id="op_3", + ts="2025-01-01T00:00:00Z", + provider="unity", + op="unity.drop_catalog", + target="c1", + payload={}, + ) + safe, risky, destructive = _classify_complete_rollback_safety(ops, validator, None) + assert len(safe) == 1 + assert len(risky) == 1 + assert len(destructive) == 1 + + +# ── _build_pre_deployment_state ──────────────────────────────────────── + + +class _RepoStub: + def __init__(self, snapshots: dict | None = None): + self._snapshots = snapshots or {} + + def read_snapshot(self, *, workspace, version): + return self._snapshots[version] + + +class TestBuildPreDeploymentState: + def test_no_previous_deployment_returns_initial(self): + provider = Mock() + provider.create_initial_state.return_value = {"catalogs": []} + tracker = Mock() + tracker.get_deployment_by_id.return_value = None + tracker.get_previous_deployment.return_value = None + deployment = {"previousDeploymentId": None} + + state = _build_pre_deployment_state( + Path("/tmp"), _RepoStub(), provider, tracker, "dev", "d1", deployment + ) + assert state == {"catalogs": []} + provider.create_initial_state.assert_called() + + def test_previous_deployment_without_ops_details_returns_initial(self): + provider = Mock() + provider.create_initial_state.return_value = {"catalogs": []} + tracker = Mock() + tracker.get_deployment_by_id.return_value = {"id": "d0"} # No opsDetails key + deployment = {"previousDeploymentId": "d0"} + + _build_pre_deployment_state( + Path("/tmp"), _RepoStub(), provider, tracker, "dev", "d1", deployment + ) + provider.create_initial_state.assert_called() + + def test_previous_deployment_with_ops_details(self): + provider = Mock() + provider.create_initial_state.return_value = {"catalogs": []} + provider.apply_operations.return_value = {"catalogs": [{"id": "c1"}]} + + prev_deployment = { + "id": "d0", + "fromVersion": "v0.1.0", + "opsDetails": [ + { + "id": "prev_op_1", + "status": "success", + "executionOrder": 1, + "type": "unity.add_catalog", + "target": "c1", + "payload": {"name": "cat"}, + } + ], + } + tracker = Mock() + tracker.get_deployment_by_id.return_value = prev_deployment + deployment = {"previousDeploymentId": "d0"} + + repo = _RepoStub(snapshots={"v0.1.0": {"state": {"catalogs": []}}}) + _build_pre_deployment_state(Path("/tmp"), repo, provider, tracker, "dev", "d1", deployment) + provider.apply_operations.assert_called_once() + + def test_uses_get_previous_deployment_fallback(self): + provider = Mock() + provider.create_initial_state.return_value = {"catalogs": []} + tracker = Mock() + tracker.get_deployment_by_id.return_value = None + tracker.get_previous_deployment.return_value = { + "id": "d0", + "fromVersion": None, + "opsDetails": [ + { + "id": "prev_op_1", + "status": "success", + "executionOrder": 1, + "type": "unity.add_catalog", + "target": "c1", + "payload": {"name": "cat"}, + } + ], + } + provider.apply_operations.return_value = {"catalogs": [{"id": "c1"}]} + deployment = {"previousDeploymentId": "d_missing"} + + _build_pre_deployment_state( + Path("/tmp"), _RepoStub(), provider, tracker, "dev", "d1", deployment + ) + tracker.get_previous_deployment.assert_called_once() + provider.apply_operations.assert_called_once() + + def test_filters_unsuccessful_ops(self): + provider = Mock() + provider.create_initial_state.return_value = {"catalogs": []} + provider.apply_operations.return_value = {"catalogs": [{"id": "c1"}]} + + prev_deployment = { + "id": "d0", + "fromVersion": None, + "opsDetails": [ + { + "id": "op_ok", + "status": "success", + "executionOrder": 1, + "type": "unity.add_catalog", + "target": "c1", + "payload": {}, + }, + { + "id": "op_fail", + "status": "failed", + "executionOrder": 2, + "type": "unity.add_schema", + "target": "s1", + "payload": {}, + }, + ], + } + tracker = Mock() + tracker.get_deployment_by_id.return_value = prev_deployment + deployment = {"previousDeploymentId": "d0"} + + _build_pre_deployment_state( + Path("/tmp"), _RepoStub(), provider, tracker, "dev", "d1", deployment + ) + # Only successful ops should be passed + call_args = provider.apply_operations.call_args + ops_passed = call_args[0][1] + assert len(ops_passed) == 1 + + +# ── RollbackResult dataclass ────────────────────────────────────────── + + +class TestRollbackResult: + def test_success_result(self): + r = RollbackResult(success=True, operations_rolled_back=3) + assert r.success is True + assert r.error_message is None + + def test_failure_result(self): + r = RollbackResult(success=False, operations_rolled_back=0, error_message="fail") + assert r.error_message == "fail" + + +# ── rollback_complete (higher-level) ─────────────────────────────────── + + +class _FullRepoStub: + def __init__( + self, *, env_config=None, snapshots=None, project=None, changelog=None, state_result=None + ): + self._env_config = env_config or {"topLevelName": "dev_cat"} + self._snapshots = snapshots or {} + self._project = project or {"name": "test"} + self._changelog = changelog or {"ops": []} + self._state_result = state_result + + def read_project(self, *, workspace): + return self._project + + def get_environment_config(self, *, project, environment): + return self._env_config + + def load_current_state(self, *, workspace, validate=False): + return self._state_result + + def read_snapshot(self, *, workspace, version): + return self._snapshots[version] + + def read_changelog(self, *, workspace): + return self._changelog + + +class TestRollbackCompleteEdgeCases: + def test_rollback_before_baseline_with_force_raises_on_user_cancel(self): + repo = _FullRepoStub( + env_config={"topLevelName": "dev_cat", "importBaselineSnapshot": "v0.2.0"}, + snapshots={"v0.0.1": {"state": {"catalogs": []}, "operations": []}}, + ) + with pytest.raises(RollbackError, match="before the import baseline"): + rollback_complete( + workspace=Path("/tmp"), + target_env="dev", + to_snapshot="v0.0.1", + profile="DEFAULT", + warehouse_id="wh_1", + force=False, + workspace_repo=repo, + ) diff --git a/packages/python-sdk/tests/unit/test_snapshot_rebase_extended.py b/packages/python-sdk/tests/unit/test_snapshot_rebase_extended.py new file mode 100644 index 0000000..658cdd3 --- /dev/null +++ b/packages/python-sdk/tests/unit/test_snapshot_rebase_extended.py @@ -0,0 +1,471 @@ +""" +Extended tests for snapshot rebase — covers internal functions for coverage. +""" + +import json +from pathlib import Path +from unittest.mock import Mock + +import pytest + +from schemax.commands.snapshot_rebase import ( + RebaseError, + _clear_changelog, + _has_conflict, + _load_rebase_context, + _persist_conflict_result, + _persist_success_snapshot, + _print_conflict_summary, + _remove_snapshot_from_project, + _replay_operations_on_base, + _save_applied_ops_to_changelog, + _save_conflict_log, + _save_to_temp_changelog, + _show_conflict_details, + rebase_snapshot, +) +from schemax.providers.base.operations import Operation + +# ── Helpers ──────────────────────────────────────────────────────────── + + +class _FakeRepo: + """Minimal repo stub for rebase tests.""" + + def __init__( + self, + *, + project: dict | None = None, + snapshots: dict[str, dict] | None = None, + changelog: dict | None = None, + state_result: tuple | None = None, + ): + self._project = project or {"name": "t", "snapshots": [], "latestSnapshot": None} + self._snapshots = snapshots or {} + self._changelog = changelog or {"ops": []} + self._state_result = state_result + self.written_snapshots: list[dict] = [] + self.written_projects: list[dict] = [] + self.written_changelogs: list[dict] = [] + + def read_project(self, *, workspace: Path) -> dict: + del workspace + return self._project + + def write_project(self, *, workspace: Path, project: dict) -> None: + del workspace + self._project = project + self.written_projects.append(project) + + def read_snapshot(self, *, workspace: Path, version: str) -> dict: + del workspace + return self._snapshots[version] + + def write_snapshot(self, *, workspace: Path, snapshot: dict) -> None: + del workspace + self.written_snapshots.append(snapshot) + + def read_changelog(self, *, workspace: Path) -> dict: + del workspace + return self._changelog + + def write_changelog(self, *, workspace: Path, changelog: dict) -> None: + del workspace + self._changelog = changelog + self.written_changelogs.append(changelog) + + def load_current_state(self, *, workspace: Path, validate: bool = False) -> tuple: + del workspace, validate + return self._state_result # type: ignore[return-value] + + def snapshot_file_path(self, *, workspace: Path, version: str) -> Path: + return workspace / ".schemax" / "snapshots" / f"{version}.json" + + +def _op_dict(op: str = "unity.add_column", target: str = "col_1", **extra) -> dict: + return { + "id": extra.pop("id", "op_1"), + "ts": "2025-01-01T00:00:00Z", + "provider": "unity", + "op": op, + "target": target, + "payload": extra.pop("payload", {}), + **extra, + } + + +def _operation(op: str = "unity.add_column", target: str = "col_1", **extra) -> Operation: + return Operation(**_op_dict(op, target, **extra)) + + +# ── _has_conflict ────────────────────────────────────────────────────── + + +class TestHasConflict: + def test_conflict_when_same_target(self): + operation = _operation(target="col_x") + base_ops = [{"target": "col_x", "op": "unity.update_column"}] + assert _has_conflict(operation, base_ops) is True + + def test_no_conflict_different_targets(self): + operation = _operation(target="col_x") + base_ops = [{"target": "col_y", "op": "unity.update_column"}] + assert _has_conflict(operation, base_ops) is False + + def test_no_conflict_empty_base(self): + assert _has_conflict(_operation(), []) is False + + def test_conflict_multiple_base_ops(self): + operation = _operation(target="tbl_1") + base_ops = [ + {"target": "tbl_2", "op": "unity.add_table"}, + {"target": "tbl_1", "op": "unity.update_table"}, + ] + assert _has_conflict(operation, base_ops) is True + + +# ── _load_rebase_context ────────────────────────────────────────────── + + +class TestLoadRebaseContext: + def test_raises_when_no_previous_snapshot(self): + repo = _FakeRepo(snapshots={"v0.2.0": {"state": {}, "operations": []}}) + with pytest.raises(RebaseError, match="no previousSnapshot"): + _load_rebase_context(Path("/tmp"), "v0.2.0", None, repo) + + def test_raises_when_no_latest_snapshot(self): + repo = _FakeRepo( + snapshots={"v0.2.0": {"previousSnapshot": "v0.1.0", "state": {}, "operations": []}}, + project={"latestSnapshot": None, "snapshots": []}, + ) + with pytest.raises(RebaseError, match="No snapshots available"): + _load_rebase_context(Path("/tmp"), "v0.2.0", None, repo) + + def test_no_rebase_needed_same_base(self): + repo = _FakeRepo( + snapshots={"v0.2.0": {"previousSnapshot": "v0.1.0", "state": {}, "operations": []}} + ) + ctx = _load_rebase_context(Path("/tmp"), "v0.2.0", "v0.1.0", repo) + assert ctx.rebase_needed is False + assert ctx.old_base == "v0.1.0" + assert ctx.new_base_version == "v0.1.0" + + def test_rebase_needed(self): + ops = [_op_dict()] + repo = _FakeRepo( + snapshots={"v0.2.0": {"previousSnapshot": "v0.1.0", "state": {}, "operations": ops}} + ) + ctx = _load_rebase_context(Path("/tmp"), "v0.2.0", "v0.1.5", repo) + assert ctx.rebase_needed is True + assert ctx.old_base == "v0.1.0" + assert ctx.new_base_version == "v0.1.5" + assert ctx.feature_ops == ops + + def test_auto_detects_latest_snapshot(self): + repo = _FakeRepo( + snapshots={"v0.2.0": {"previousSnapshot": "v0.1.0", "state": {}, "operations": []}}, + project={"latestSnapshot": "v0.1.5", "snapshots": []}, + ) + ctx = _load_rebase_context(Path("/tmp"), "v0.2.0", None, repo) + assert ctx.rebase_needed is True + assert ctx.new_base_version == "v0.1.5" + + +# ── _replay_operations_on_base ───────────────────────────────────────── + + +class TestReplayOperationsOnBase: + def test_all_ops_applied_no_conflict(self): + provider = Mock() + provider.apply_operations.return_value = {"catalogs": [{"id": "c1"}]} + ops = [_op_dict(target="col_1"), _op_dict(target="col_2", id="op_2")] + result = _replay_operations_on_base(ops, {"catalogs": []}, [], provider, "v0.1.0") + assert result.success is True + assert len(result.applied_ops) == 2 + assert result.conflicting_ops == [] + + def test_conflict_on_same_target(self): + provider = Mock() + provider.apply_operations.return_value = {"catalogs": []} + ops = [_op_dict(target="col_x")] + base_ops = [{"target": "col_x", "op": "unity.update_column"}] + result = _replay_operations_on_base(ops, {"catalogs": []}, base_ops, provider, "v0.1.0") + assert result.success is False + assert len(result.conflicting_ops) == 1 + assert "changed in v0.1.0" in result.conflicting_ops[0]["reason"] + + def test_subsequent_ops_blocked_after_conflict(self): + provider = Mock() + provider.apply_operations.return_value = {"catalogs": []} + ops = [ + _op_dict(target="col_x", id="op_1"), + _op_dict(target="col_y", id="op_2"), + _op_dict(target="col_z", id="op_3"), + ] + base_ops = [{"target": "col_x", "op": "unity.update_column"}] + result = _replay_operations_on_base(ops, {"catalogs": []}, base_ops, provider, "v0.1.0") + assert result.success is False + assert len(result.conflicting_ops) == 3 + assert result.conflicting_ops[1]["reason"] == "Blocked by previous conflict" + assert result.conflicting_ops[2]["reason"] == "Blocked by previous conflict" + + def test_provider_error_treated_as_conflict(self): + provider = Mock() + provider.apply_operations.side_effect = RuntimeError("boom") + ops = [_op_dict()] + result = _replay_operations_on_base(ops, {"catalogs": []}, [], provider, "v0.1.0") + assert result.success is False + assert "Error: boom" in result.conflicting_ops[0]["reason"] + + +# ── changelog helpers ─────────────────────────────────────────────────── + + +class TestChangelogHelpers: + def test_clear_changelog(self): + repo = _FakeRepo( + changelog={"ops": [{"id": "x"}], "_rebase_temp": True, "_rebase_message": "msg"} + ) + _clear_changelog(Path("/tmp"), "v0.1.0", repo) + cl = repo._changelog + assert cl["ops"] == [] + assert cl["sinceSnapshot"] == "v0.1.0" + assert "_rebase_temp" not in cl + assert "_rebase_message" not in cl + + def test_save_applied_ops(self): + repo = _FakeRepo(changelog={"ops": [], "_rebase_temp": True, "_rebase_message": "old"}) + ops = [_op_dict()] + _save_applied_ops_to_changelog(Path("/tmp"), ops, "v0.2.0", repo) + cl = repo._changelog + assert cl["ops"] == ops + assert cl["sinceSnapshot"] == "v0.2.0" + assert "_rebase_temp" not in cl + + def test_save_to_temp_changelog(self): + repo = _FakeRepo() + ops = [_op_dict()] + _save_to_temp_changelog(Path("/tmp"), ops, "v0.2.0", repo) + cl = repo._changelog + assert cl["_rebase_temp"] is True + assert cl["ops"] == ops + + +# ── _save_conflict_log ────────────────────────────────────────────────── + + +class TestSaveConflictLog: + def test_writes_json_file(self, tmp_path): + path = _save_conflict_log( + tmp_path, + "v0.4.0", + "v0.3.0", + "v0.3.1", + applied_ops=[_op_dict()], + conflicting_ops=[{"operation": _op_dict(id="op_c"), "index": 1, "reason": "boom"}], + ) + full = tmp_path / path + assert full.exists() + data = json.loads(full.read_text()) + assert data["snapshot_version"] == "v0.4.0" + assert data["old_base"] == "v0.3.0" + assert data["new_base"] == "v0.3.1" + assert len(data["applied_operations"]) == 1 + assert len(data["conflicting_operations"]) == 1 + + +# ── _remove_snapshot_from_project ─────────────────────────────────────── + + +class TestRemoveSnapshotFromProject: + def test_removes_matching_version(self): + repo = _FakeRepo( + project={ + "snapshots": [{"version": "v0.1.0"}, {"version": "v0.2.0"}], + "latestSnapshot": "v0.2.0", + } + ) + _remove_snapshot_from_project(Path("/tmp"), "v0.2.0", repo) + assert len(repo._project["snapshots"]) == 1 + assert repo._project["latestSnapshot"] == "v0.1.0" + + def test_clears_latest_when_last(self): + repo = _FakeRepo(project={"snapshots": [{"version": "v0.1.0"}], "latestSnapshot": "v0.1.0"}) + _remove_snapshot_from_project(Path("/tmp"), "v0.1.0", repo) + assert repo._project["snapshots"] == [] + assert repo._project["latestSnapshot"] is None + + def test_noop_when_version_not_found(self): + repo = _FakeRepo(project={"snapshots": [{"version": "v0.1.0"}], "latestSnapshot": "v0.1.0"}) + _remove_snapshot_from_project(Path("/tmp"), "v9.9.9", repo) + assert len(repo._project["snapshots"]) == 1 + + +# ── _persist_success_snapshot ─────────────────────────────────────────── + + +class TestPersistSuccessSnapshot: + def test_writes_rebased_snapshot(self): + repo = _FakeRepo(project={"snapshots": [], "latestSnapshot": "v0.1.0"}) + snapshot = { + "id": "snap_1", + "name": "test", + "ts": "2025-01-01T00:00:00Z", + "createdBy": "user", + "previousSnapshot": "v0.1.0", + "hash": "abc", + "tags": ["t1"], + "comment": "hello", + } + _persist_success_snapshot( + Path("/tmp"), + snapshot, + "v0.2.0", + "v0.1.5", + {"catalogs": [{"id": "c1"}]}, + [_op_dict()], + repo, + ) + assert len(repo.written_snapshots) == 1 + written = repo.written_snapshots[0] + assert written["previousSnapshot"] == "v0.1.5" + assert written["rebasedFrom"] == "v0.1.0" + assert "rebasedAt" in written + assert len(repo._project["snapshots"]) == 1 + + +# ── _persist_conflict_result ──────────────────────────────────────────── + + +class TestPersistConflictResult: + def test_saves_applied_ops_and_conflict_log(self, tmp_path): + repo = _FakeRepo() + applied = [_op_dict()] + conflicting = [{"operation": _op_dict(id="op_c"), "index": 1, "reason": "boom"}] + path = _persist_conflict_result( + tmp_path, applied, conflicting, "v0.1.5", "v0.2.0", "v0.1.0", repo + ) + assert repo._changelog["ops"] == applied + assert (tmp_path / path).exists() + + def test_clears_changelog_when_no_applied(self, tmp_path): + repo = _FakeRepo() + conflicting = [{"operation": _op_dict(id="op_c"), "index": 0, "reason": "boom"}] + _persist_conflict_result(tmp_path, [], conflicting, "v0.1.5", "v0.2.0", "v0.1.0", repo) + assert repo._changelog["ops"] == [] + + +# ── rebase_snapshot (top-level) ───────────────────────────────────────── + + +class TestRebaseSnapshotTopLevel: + def test_no_rebase_needed(self): + repo = _FakeRepo( + snapshots={"v0.2.0": {"previousSnapshot": "v0.1.0", "state": {}, "operations": []}} + ) + result = rebase_snapshot(Path("/tmp"), "v0.2.0", "v0.1.0", workspace_repo=repo) + assert result.success is True + assert result.message == "No rebase needed" + + def test_successful_rebase(self, tmp_path): + # Set up snapshot file so unlink works + snap_dir = tmp_path / ".schemax" / "snapshots" + snap_dir.mkdir(parents=True) + (snap_dir / "v0.2.0.json").write_text("{}") + + provider = Mock() + provider.apply_operations.return_value = {"catalogs": [{"id": "c1"}]} + + repo = _FakeRepo( + snapshots={ + "v0.2.0": { + "id": "s1", + "name": "test", + "ts": "2025-01-01T00:00:00Z", + "createdBy": "user", + "previousSnapshot": "v0.1.0", + "state": {"catalogs": []}, + "operations": [_op_dict(target="col_new")], + "hash": "abc", + "tags": [], + "comment": "", + }, + "v0.1.5": { + "state": {"catalogs": []}, + "operations": [_op_dict(target="col_other", id="base_op")], + }, + }, + project={ + "snapshots": [{"version": "v0.2.0"}], + "latestSnapshot": "v0.2.0", + }, + state_result=({"catalogs": []}, {}, provider, None), + ) + result = rebase_snapshot(tmp_path, "v0.2.0", "v0.1.5", workspace_repo=repo) + assert result.success is True + assert result.applied_count == 1 + + def test_wraps_unexpected_error(self): + repo = _FakeRepo( + snapshots={"v0.2.0": {"previousSnapshot": "v0.1.0", "state": {}, "operations": []}} + ) + # Break the repo so an unexpected exception happens during rebase steps + repo.read_snapshot = Mock(side_effect=RuntimeError("disk fail")) + with pytest.raises(RebaseError, match="disk fail"): + rebase_snapshot(Path("/tmp"), "v0.2.0", "v0.1.5", workspace_repo=repo) + + def test_conflict_returns_failure(self, tmp_path): + snap_dir = tmp_path / ".schemax" / "snapshots" + snap_dir.mkdir(parents=True) + (snap_dir / "v0.2.0.json").write_text("{}") + + provider = Mock() + provider.apply_operations.return_value = {"catalogs": []} + + repo = _FakeRepo( + snapshots={ + "v0.2.0": { + "id": "s1", + "name": "test", + "ts": "2025-01-01T00:00:00Z", + "createdBy": "user", + "previousSnapshot": "v0.1.0", + "state": {"catalogs": []}, + "operations": [_op_dict(target="col_x")], + "hash": "abc", + "tags": [], + "comment": "", + }, + "v0.1.5": { + "state": {"catalogs": []}, + "operations": [{"target": "col_x", "op": "unity.update_column"}], + }, + }, + project={ + "snapshots": [{"version": "v0.2.0"}], + "latestSnapshot": "v0.2.0", + }, + state_result=({"catalogs": []}, {}, provider, None), + ) + result = rebase_snapshot(tmp_path, "v0.2.0", "v0.1.5", workspace_repo=repo) + assert result.success is False + assert result.conflict_count >= 1 + assert result.conflict_log_path is not None + + +# ── console output helpers (exercise for coverage) ────────────────────── + + +class TestConsolePrinting: + def test_show_conflict_details_runs(self, capsys): + conflict = {"operation": {"op": "unity.add_column", "target": "col_x"}, "reason": "boom"} + _show_conflict_details(conflict, "v0.1.5") + # Just verifies no exceptions; output goes to rich console + + def test_print_conflict_summary_runs(self, capsys): + conflicts = [{"operation": {"op": "unity.add_column", "target": "col_x"}, "reason": "boom"}] + _print_conflict_summary(conflicts, [_op_dict()], "/tmp/log.json", "v0.1.5", "v0.2.0") + + def test_print_conflict_summary_no_applied(self, capsys): + conflicts = [{"operation": {"op": "unity.add_column", "target": "col_x"}, "reason": "boom"}] + _print_conflict_summary(conflicts, [], "/tmp/log.json", "v0.1.5", "v0.2.0") diff --git a/packages/python-sdk/tests/unit/test_sql_command.py b/packages/python-sdk/tests/unit/test_sql_command.py index 75c989e..5aab098 100644 --- a/packages/python-sdk/tests/unit/test_sql_command.py +++ b/packages/python-sdk/tests/unit/test_sql_command.py @@ -1,141 +1,272 @@ -"""Unit tests for SQL command module.""" +"""Tests for commands/sql.py — covers build_catalog_mapping and helper functions.""" from pathlib import Path +from types import SimpleNamespace from unittest.mock import Mock import pytest -from schemax.commands.sql import SQLGenerationError, build_catalog_mapping, generate_sql_migration - - -def _make_op(op_id: str = "op_1") -> dict: - return { - "id": op_id, - "ts": "2026-02-01T00:00:00Z", - "provider": "unity", - "op": "unity.add_catalog", - "target": "cat_1", - "payload": {"catalogId": "cat_1", "name": "demo"}, - } - - -class _RepoStub: - def __init__( - self, - *, - project: dict, - state_result: tuple | None = None, - snapshots: dict[str, dict] | None = None, - env_config: dict | None = None, - ) -> None: - self._project = project +from schemax.commands.sql import ( + SQLGenerationError, + _extract_add_table_payload, + _prepare_operations, + build_catalog_mapping, + generate_sql_migration, +) +from schemax.providers.base.operations import Operation + +# ── build_catalog_mapping ────────────────────────────────────────────── + + +class TestBuildCatalogMapping: + def test_no_catalogs(self): + result = build_catalog_mapping({"catalogs": []}, {}) + assert result == {} + + def test_valid_mapping(self): + state = {"catalogs": [{"name": "analytics"}]} + env = {"catalogMappings": {"analytics": "prod_analytics"}} + result = build_catalog_mapping(state, env) + assert result == {"analytics": "prod_analytics"} + + def test_missing_mapping_raises(self): + state = {"catalogs": [{"name": "analytics"}, {"name": "raw"}]} + env = {"catalogMappings": {"analytics": "prod_analytics"}} + with pytest.raises(SQLGenerationError, match="Missing catalog mapping"): + build_catalog_mapping(state, env) + + def test_non_dict_mappings_raises(self): + state = {"catalogs": [{"name": "analytics"}]} + env = {"catalogMappings": "not_a_dict"} + with pytest.raises(SQLGenerationError, match="must be an object"): + build_catalog_mapping(state, env) + + def test_empty_mappings_dict(self): + state = {"catalogs": [{"name": "cat1"}]} + env = {"catalogMappings": {}} + with pytest.raises(SQLGenerationError, match="Missing catalog mapping"): + build_catalog_mapping(state, env) + + +# ── _extract_add_table_payload ───────────────────────────────────────── + + +class TestExtractAddTablePayload: + def test_non_add_table_op_returns_none(self): + op = Operation( + id="op1", + ts="2025-01-01T00:00:00Z", + provider="unity", + op="unity.add_schema", + target="s1", + payload={}, + ) + assert _extract_add_table_payload(op) is None + + def test_non_external_table_returns_none(self): + op = Operation( + id="op1", + ts="2025-01-01T00:00:00Z", + provider="unity", + op="unity.add_table", + target="t1", + payload={"name": "tbl"}, + ) + assert _extract_add_table_payload(op) is None + + def test_external_table_operation(self): + op = Operation( + id="op1", + ts="2025-01-01T00:00:00Z", + provider="unity", + op="unity.add_table", + target="t1", + payload={ + "name": "ext_tbl", + "external": True, + "externalLocationName": "loc1", + "path": "data/", + }, + ) + result = _extract_add_table_payload(op) + assert result == ("ext_tbl", "loc1", "data/") + + def test_dict_operation_non_external(self): + assert ( + _extract_add_table_payload({"op": "unity.add_table", "payload": {"name": "t"}}) is None + ) + + def test_dict_operation_non_add_table(self): + assert _extract_add_table_payload({"op": "unity.add_schema"}) is None + + def test_dict_external_table(self): + result = _extract_add_table_payload( + { + "op": "unity.add_table", + "payload": { + "name": "ext", + "external": True, + "externalLocationName": "loc", + "path": "p/", + }, + } + ) + assert result == ("ext", "loc", "p/") + + +# ── _prepare_operations ─────────────────────────────────────────────── + + +class TestPrepareOperations: + def test_converts_dicts_to_operations(self): + ops = [ + { + "id": "op1", + "ts": "2025-01-01T00:00:00Z", + "provider": "unity", + "op": "unity.add_table", + "target": "t1", + "payload": {}, + } + ] + result = _prepare_operations(ops, None, None) + assert isinstance(result[0], Operation) + + def test_passes_through_operation_objects(self): + op = Operation( + id="op1", + ts="2025-01-01T00:00:00Z", + provider="unity", + op="unity.add_table", + target="t1", + payload={}, + ) + result = _prepare_operations([op], None, None) + assert result[0] is op + + +# ── generate_sql_migration ───────────────────────────────────────────── + + +class _FakeRepo: + def __init__(self, *, project=None, state_result=None, snapshots=None, env_config=None): + self._project = project or {"name": "test"} self._state_result = state_result self._snapshots = snapshots or {} self._env_config = env_config or {} - def read_project(self, *, workspace: Path) -> dict: - del workspace + def read_project(self, *, workspace): return self._project - def load_current_state(self, *, workspace: Path, validate: bool = False) -> tuple: - del workspace, validate - if self._state_result is None: - raise AssertionError("state_result not configured") + def load_current_state(self, *, workspace, validate=False): return self._state_result - def read_snapshot(self, *, workspace: Path, version: str) -> dict: - del workspace + def read_snapshot(self, *, workspace, version): return self._snapshots[version] - def get_environment_config(self, *, project: dict, environment: str) -> dict: - del project, environment + def get_environment_config(self, *, project, environment): return self._env_config -def test_build_catalog_mapping_empty_state_returns_empty_mapping() -> None: - assert build_catalog_mapping({"catalogs": []}, {"catalogMappings": {}}) == {} - - -def test_build_catalog_mapping_requires_object_mappings() -> None: - with pytest.raises(SQLGenerationError, match="must be an object"): - build_catalog_mapping({"catalogs": [{"name": "demo"}]}, {"catalogMappings": "bad"}) - - -def test_build_catalog_mapping_requires_all_catalogs() -> None: - state = {"catalogs": [{"name": "demo"}, {"name": "analytics"}]} - env_config = {"catalogMappings": {"demo": "dev_demo"}} - - with pytest.raises(SQLGenerationError, match=r"Missing catalog mapping\(s\)"): - build_catalog_mapping(state, env_config) - - -def test_generate_sql_migration_returns_empty_when_no_ops(monkeypatch: pytest.MonkeyPatch) -> None: - del monkeypatch - provider = Mock() - provider.info.name = "Unity Catalog" - provider.info.version = "1.0.0" - - repo = _RepoStub( - project={ - "provider": {"environments": {"dev": {"topLevelName": "dev_demo"}}}, - "managedLocations": {}, - "externalLocations": {}, - }, - state_result=({"catalogs": []}, {"ops": []}, provider, None), - ) - - assert generate_sql_migration(workspace=Path("."), workspace_repo=repo) == "" - - -def test_generate_sql_migration_with_target_and_output( - monkeypatch: pytest.MonkeyPatch, tmp_path: Path -) -> None: - del monkeypatch - provider = Mock() - provider.info.name = "Unity Catalog" - provider.info.version = "1.0.0" - - generator = Mock() - generator.generate_sql.return_value = "CREATE CATALOG `dev_demo`;" - provider.get_sql_generator.return_value = generator - - state = {"catalogs": [{"name": "demo"}]} - changelog = {"ops": [_make_op()]} - project = { - "provider": {"environments": {"dev": {"topLevelName": "dev_demo"}}}, - "managedLocations": {}, - "externalLocations": {}, - } - repo = _RepoStub( - project=project, - state_result=(state, changelog, provider, None), - env_config={"topLevelName": "dev_demo", "catalogMappings": {"demo": "dev_demo"}}, - ) - - out_file = tmp_path / "migration.sql" - sql = generate_sql_migration( - workspace=tmp_path, - output=out_file, - target_env="dev", - workspace_repo=repo, - ) - - assert sql == "CREATE CATALOG `dev_demo`;" - assert out_file.read_text() == "CREATE CATALOG `dev_demo`;" - generator.generate_sql.assert_called_once() - - -def test_generate_sql_migration_snapshot_latest_requires_existing_snapshot( - monkeypatch: pytest.MonkeyPatch, -) -> None: - del monkeypatch - repo = _RepoStub( - project={ - "latestSnapshot": None, - "managedLocations": {}, - "externalLocations": {}, - }, - ) - - with pytest.raises(SQLGenerationError, match="No snapshots available"): - generate_sql_migration(workspace=Path("."), snapshot="latest", workspace_repo=repo) +class TestGenerateSqlMigration: + def test_no_ops_returns_empty(self): + provider = Mock() + provider.info = SimpleNamespace(name="Unity Catalog") + repo = _FakeRepo( + state_result=({"catalogs": []}, {"ops": []}, provider, None), + ) + result = generate_sql_migration(Path("/tmp"), workspace_repo=repo) + assert result == "" + + def test_with_ops_generates_sql(self): + provider = Mock() + provider.info = SimpleNamespace(name="Unity Catalog") + provider.get_sql_generator.return_value.generate_sql.return_value = "CREATE CATALOG demo;" + + ops = [ + { + "id": "op1", + "ts": "2025-01-01T00:00:00Z", + "provider": "unity", + "op": "unity.add_catalog", + "target": "c1", + "payload": {"name": "demo"}, + } + ] + repo = _FakeRepo( + state_result=({"catalogs": []}, {"ops": ops}, provider, None), + ) + result = generate_sql_migration(Path("/tmp"), workspace_repo=repo) + assert "CREATE CATALOG demo" in result + + def test_file_not_found_wraps_error(self): + repo = _FakeRepo() + repo.read_project = Mock(side_effect=FileNotFoundError("no project")) + with pytest.raises(SQLGenerationError, match="Project files not found"): + generate_sql_migration(Path("/tmp"), workspace_repo=repo) + + def test_unexpected_error_wraps(self): + repo = _FakeRepo() + repo.read_project = Mock(side_effect=RuntimeError("boom")) + with pytest.raises(SQLGenerationError, match="Failed to generate SQL"): + generate_sql_migration(Path("/tmp"), workspace_repo=repo) + + def test_snapshot_latest_no_snapshots_raises(self): + repo = _FakeRepo(project={"name": "test", "latestSnapshot": None}) + with pytest.raises(SQLGenerationError, match="No snapshots available"): + generate_sql_migration(Path("/tmp"), snapshot="latest", workspace_repo=repo) + + def test_snapshot_latest_with_ops(self): + provider = Mock() + provider.info = SimpleNamespace(name="Unity Catalog") + provider.get_sql_generator.return_value.generate_sql.return_value = "ALTER TABLE t1;" + ops = [ + { + "id": "op1", + "ts": "2025-01-01T00:00:00Z", + "provider": "unity", + "op": "unity.add_column", + "target": "col1", + "payload": {}, + } + ] + repo = _FakeRepo( + project={"name": "test", "latestSnapshot": "v0.1.0"}, + snapshots={"v0.1.0": {"state": {"catalogs": []}, "operations": ops}}, + state_result=({"catalogs": []}, {"ops": []}, provider, None), + ) + result = generate_sql_migration(Path("/tmp"), snapshot="latest", workspace_repo=repo) + assert "ALTER TABLE t1" in result + + def test_snapshot_no_operations_raises(self): + provider = Mock() + repo = _FakeRepo( + project={"name": "test", "latestSnapshot": "v0.1.0"}, + snapshots={"v0.1.0": {"state": {"catalogs": []}}}, + state_result=({"catalogs": []}, {"ops": []}, provider, None), + ) + with pytest.raises(SQLGenerationError, match="not yet supported"): + generate_sql_migration(Path("/tmp"), snapshot="latest", workspace_repo=repo) + + def test_output_to_file(self, tmp_path): + provider = Mock() + provider.info = SimpleNamespace(name="Unity Catalog") + provider.get_sql_generator.return_value.generate_sql.return_value = "CREATE TABLE t1;" + + ops = [ + { + "id": "op1", + "ts": "2025-01-01T00:00:00Z", + "provider": "unity", + "op": "unity.add_table", + "target": "t1", + "payload": {}, + } + ] + repo = _FakeRepo( + state_result=({"catalogs": []}, {"ops": ops}, provider, None), + ) + output = tmp_path / "out.sql" + generate_sql_migration(Path("/tmp"), output=output, workspace_repo=repo) + assert output.exists() + assert output.read_text() == "CREATE TABLE t1;" diff --git a/packages/python-sdk/tests/unit/test_sql_parser_extended.py b/packages/python-sdk/tests/unit/test_sql_parser_extended.py new file mode 100644 index 0000000..d3f80e0 --- /dev/null +++ b/packages/python-sdk/tests/unit/test_sql_parser_extended.py @@ -0,0 +1,301 @@ +""" +Extended tests for sql_parser.py — covers internal functions for coverage. +""" + +from schemax.providers.base.sql_parser import ( + _parse_table_reference, + _resolve_from_schema, + extract_column_references, + extract_dependencies_from_view, + extract_table_references, + resolve_table_or_view, + validate_sql_syntax, +) + +# ── extract_table_references ─────────────────────────────────────────── + + +class TestExtractTableReferences: + def test_simple_select(self): + result = extract_table_references("SELECT * FROM catalog.schema.tbl") + assert "catalog.schema.tbl" in result["all"][0] + + def test_join_two_tables(self): + sql = "SELECT * FROM a.b.t1 JOIN a.b.t2 ON t1.id = t2.id" + result = extract_table_references(sql) + assert len(result["all"]) == 2 + + def test_returns_empty_on_parse_error(self): + result = extract_table_references("NOT VALID SQL ;;; ///") + assert result == {"tables": [], "views": [], "all": []} + + def test_table_without_catalog(self): + result = extract_table_references("SELECT * FROM my_table") + assert len(result["all"]) >= 1 + assert "my_table" in result["all"][0] + + def test_backtick_identifiers(self): + result = extract_table_references("SELECT * FROM `cat`.`sch`.`tbl`") + assert len(result["all"]) >= 1 + + def test_views_always_empty(self): + result = extract_table_references("SELECT * FROM x.y.z") + assert result["views"] == [] + + def test_tables_copy_of_all(self): + result = extract_table_references("SELECT * FROM a.b.c") + assert result["tables"] == result["all"] + + +# ── extract_column_references ────────────────────────────────────────── + + +class TestExtractColumnReferences: + def test_simple_columns(self): + cols = extract_column_references("SELECT id, name FROM tbl") + # Should find at least id and name + col_text = " ".join(cols) + assert "id" in col_text + assert "name" in col_text + + def test_qualified_columns(self): + cols = extract_column_references("SELECT t.id FROM tbl AS t") + assert any("id" in c for c in cols) + + def test_returns_empty_on_error(self): + assert extract_column_references("NOT VALID SQL ;;; ///") == [] + + +# ── validate_sql_syntax ──────────────────────────────────────────────── + + +class TestValidateSqlSyntax: + def test_valid_sql(self): + valid, msg = validate_sql_syntax("SELECT 1") + assert valid is True + assert msg == "" + + def test_invalid_sql(self): + valid, msg = validate_sql_syntax("SELECT FROM WHERE") + # SQLGlot may or may not error — if it does, we get a message + # Some invalid SQL is still parseable by SQLGlot, so just check the tuple shape + assert isinstance(valid, bool) + assert isinstance(msg, str) + + def test_empty_sql(self): + valid, msg = validate_sql_syntax("") + # Empty string may return False/"None" or True depending on SQLGlot version + assert isinstance(valid, bool) + + +# ── _parse_table_reference ───────────────────────────────────────────── + + +class TestParseTableReference: + def test_three_parts(self): + result = _parse_table_reference("cat.sch.tbl", None) + assert result == ("cat", "sch", "tbl") + + def test_two_parts_with_default_catalog(self): + result = _parse_table_reference("sch.tbl", "my_cat") + assert result == ("my_cat", "sch", "tbl") + + def test_two_parts_no_default(self): + result = _parse_table_reference("sch.tbl", None) + assert result == ("__implicit__", "sch", "tbl") + + def test_one_part(self): + result = _parse_table_reference("tbl", "my_cat") + assert result == ("my_cat", None, "tbl") + + def test_one_part_no_default(self): + result = _parse_table_reference("tbl", None) + assert result == ("__implicit__", None, "tbl") + + def test_too_many_parts_returns_none(self): + result = _parse_table_reference("a.b.c.d", None) + assert result is None + + +# ── _resolve_from_schema ────────────────────────────────────────────── + + +class TestResolveFromSchema: + def test_finds_table(self): + schema = {"tables": [{"name": "t1", "id": "tid_1"}], "views": []} + assert _resolve_from_schema(schema, "t1") == ("table", "tid_1") + + def test_finds_view(self): + schema = {"tables": [], "views": [{"name": "v1", "id": "vid_1"}]} + assert _resolve_from_schema(schema, "v1") == ("view", "vid_1") + + def test_returns_none_when_not_found(self): + schema = {"tables": [{"name": "t1", "id": "tid_1"}], "views": []} + assert _resolve_from_schema(schema, "missing") is None + + def test_empty_schema(self): + assert _resolve_from_schema({}, "anything") is None + + def test_table_without_id(self): + schema = {"tables": [{"name": "t1"}], "views": []} + assert _resolve_from_schema(schema, "t1") == ("table", "") + + +# ── resolve_table_or_view ───────────────────────────────────────────── + + +class TestResolveTableOrView: + def test_resolves_table(self): + state = { + "catalogs": [ + { + "name": "cat", + "schemas": [ + {"name": "sch", "tables": [{"name": "tbl", "id": "t1"}], "views": []} + ], + } + ] + } + obj_type, obj_id = resolve_table_or_view("cat.sch.tbl", state) + assert obj_type == "table" + assert obj_id == "t1" + + def test_resolves_view(self): + state = { + "catalogs": [ + { + "name": "cat", + "schemas": [ + {"name": "sch", "tables": [], "views": [{"name": "vw", "id": "v1"}]} + ], + } + ] + } + obj_type, obj_id = resolve_table_or_view("cat.sch.vw", state) + assert obj_type == "view" + assert obj_id == "v1" + + def test_returns_table_when_not_found(self): + state = {"catalogs": []} + obj_type, obj_id = resolve_table_or_view("cat.sch.unknown", state) + assert obj_type == "table" + assert obj_id == "" + + def test_invalid_reference(self): + state = {"catalogs": []} + obj_type, obj_id = resolve_table_or_view("a.b.c.d", state) + assert obj_type == "table" + assert obj_id == "" + + def test_with_default_catalog(self): + state = { + "catalogs": [ + { + "name": "my_cat", + "schemas": [ + {"name": "sch", "tables": [{"name": "tbl", "id": "t1"}], "views": []} + ], + } + ] + } + obj_type, obj_id = resolve_table_or_view("sch.tbl", state, default_catalog="my_cat") + assert obj_type == "table" + assert obj_id == "t1" + + def test_catalog_mismatch(self): + state = { + "catalogs": [ + { + "name": "other_cat", + "schemas": [ + {"name": "sch", "tables": [{"name": "tbl", "id": "t1"}], "views": []} + ], + } + ] + } + obj_type, obj_id = resolve_table_or_view("cat.sch.tbl", state) + assert obj_type == "table" + assert obj_id == "" + + def test_schema_mismatch(self): + state = { + "catalogs": [ + { + "name": "cat", + "schemas": [ + {"name": "other_sch", "tables": [{"name": "tbl", "id": "t1"}], "views": []} + ], + } + ] + } + obj_type, obj_id = resolve_table_or_view("cat.sch.tbl", state) + assert obj_type == "table" + assert obj_id == "" + + +# ── extract_dependencies_from_view ───────────────────────────────────── + + +class TestExtractDependenciesFromView: + def test_resolves_table_dependency(self): + state = { + "catalogs": [ + { + "name": "cat", + "schemas": [ + {"name": "sch", "tables": [{"name": "src", "id": "t1"}], "views": []} + ], + } + ] + } + deps = extract_dependencies_from_view( + "SELECT * FROM cat.sch.src", state, default_catalog="cat" + ) + assert "t1" in deps["tables"] + assert deps["views"] == [] + + def test_resolves_view_dependency(self): + state = { + "catalogs": [ + { + "name": "cat", + "schemas": [ + {"name": "sch", "tables": [], "views": [{"name": "vw", "id": "v1"}]} + ], + } + ] + } + deps = extract_dependencies_from_view( + "SELECT * FROM cat.sch.vw", state, default_catalog="cat" + ) + assert "v1" in deps["views"] + + def test_unresolved_dependency(self): + state = {"catalogs": []} + deps = extract_dependencies_from_view("SELECT * FROM cat.sch.unknown", state) + assert len(deps["unresolved"]) >= 1 + + def test_invalid_sql_returns_empty(self): + deps = extract_dependencies_from_view("NOT VALID ;;; ///", {}) + assert deps["tables"] == [] + assert deps["views"] == [] + + def test_mixed_dependencies(self): + state = { + "catalogs": [ + { + "name": "cat", + "schemas": [ + { + "name": "sch", + "tables": [{"name": "t1", "id": "tid1"}], + "views": [{"name": "v1", "id": "vid1"}], + } + ], + } + ] + } + sql = "SELECT * FROM cat.sch.t1 JOIN cat.sch.v1 ON t1.id = v1.id" + deps = extract_dependencies_from_view(sql, state) + assert "tid1" in deps["tables"] + assert "vid1" in deps["views"] diff --git a/packages/python-sdk/tests/unit/test_storage_v4.py b/packages/python-sdk/tests/unit/test_storage_v4.py index c996047..4ac74dc 100644 --- a/packages/python-sdk/tests/unit/test_storage_v4.py +++ b/packages/python-sdk/tests/unit/test_storage_v4.py @@ -48,13 +48,14 @@ def test_ensure_project_file_creates_new_v4_project(self, tmp_path): with open(project_file) as f: project = json.load(f) - # Check v4 schema - assert project["version"] == 4 - assert "provider" in project - assert "environments" in project["provider"] + # Check v5 schema + assert project["version"] == 5 + assert "targets" in project + default_target = project["targets"]["default"] + assert "environments" in default_target # Check default environments - envs = project["provider"]["environments"] + envs = project["targets"]["default"]["environments"] assert "dev" in envs assert "test" in envs assert "prod" in envs @@ -133,8 +134,8 @@ def test_read_write_project(self, tmp_path): # Read project project = storage.read_project(tmp_path) - assert project["version"] == 4 - assert "provider" in project + assert project["version"] == 5 + assert "targets" in project # Modify and write project["name"] = "test_modified" @@ -230,8 +231,11 @@ class TestLegacySingleCatalogHardBreak: """Legacy implicit workspace markers must fail fast.""" def test_read_project_rejects_single_catalog_mode(self, tmp_path): + # Write a v4 project directly so read_project triggers legacy validation storage.ensure_project_file(tmp_path, "unity") project = storage.read_project(tmp_path) + project["version"] = 4 + project["provider"] = project.pop("targets")["default"] project["settings"]["catalogMode"] = "single" storage.write_project(tmp_path, project) diff --git a/packages/python-sdk/tests/unit/test_storage_v5.py b/packages/python-sdk/tests/unit/test_storage_v5.py new file mode 100644 index 0000000..5e93ae1 --- /dev/null +++ b/packages/python-sdk/tests/unit/test_storage_v5.py @@ -0,0 +1,318 @@ +""" +Unit tests for v5 multi-target project schema and v4 → v5 migration. +""" + +import json + +import pytest + +from schemax.core import storage +from schemax.providers.base.operations import Operation, create_operation + + +class TestV4ToV5Migration: + """Test transparent v4 → v5 auto-migration.""" + + def _write_v4_project(self, tmp_path, project_data=None): + """Write a v4 project file directly for migration testing.""" + storage.ensure_schemax_dir(tmp_path) + if project_data is None: + project_data = { + "version": 4, + "name": "test_workspace", + "provider": { + "type": "unity", + "version": "1.0.0", + "environments": { + "dev": { + "topLevelName": "dev_test", + "allowDrift": True, + "requireSnapshot": False, + "autoCreateTopLevel": True, + "autoCreateSchemaxSchema": True, + "catalogMappings": {"analytics": "dev_analytics"}, + }, + "prod": { + "topLevelName": "prod_test", + "allowDrift": False, + "requireSnapshot": True, + "autoCreateTopLevel": False, + "autoCreateSchemaxSchema": True, + "catalogMappings": {"analytics": "prod_analytics"}, + }, + }, + }, + **storage.default_project_skeleton_tail(), + } + project_path = storage.get_project_file_path(tmp_path) + with open(project_path, "w", encoding="utf-8") as f: + json.dump(project_data, f, indent=2) + # Also write an empty changelog + changelog_path = storage.get_changelog_file_path(tmp_path) + changelog = { + "version": 1, + "sinceSnapshot": None, + "ops": [], + "lastModified": "2024-01-01T00:00:00Z", + } + with open(changelog_path, "w", encoding="utf-8") as f: + json.dump(changelog, f, indent=2) + + def test_read_project_auto_migrates_v4_to_v5(self, tmp_path): + """read_project should transparently migrate v4 → v5.""" + self._write_v4_project(tmp_path) + project = storage.read_project(tmp_path) + + assert project["version"] == 5 + assert "targets" in project + assert "defaultTarget" in project + assert project["defaultTarget"] == "default" + assert "provider" not in project + + # Target should contain the old provider config + target = project["targets"]["default"] + assert target["type"] == "unity" + assert target["version"] == "1.0.0" + assert "dev" in target["environments"] + assert "prod" in target["environments"] + + def test_v4_migration_persists_to_disk(self, tmp_path): + """After migration, the file on disk should be v5.""" + self._write_v4_project(tmp_path) + storage.read_project(tmp_path) + + # Read raw file + project_path = storage.get_project_file_path(tmp_path) + with open(project_path, encoding="utf-8") as f: + raw = json.load(f) + + assert raw["version"] == 5 + assert "targets" in raw + assert "provider" not in raw + + def test_v4_migration_preserves_environment_config(self, tmp_path): + """Environment configs should be intact after migration.""" + self._write_v4_project(tmp_path) + project = storage.read_project(tmp_path) + + dev_config = storage.get_environment_config(project, "dev") + assert dev_config["topLevelName"] == "dev_test" + assert dev_config["allowDrift"] is True + assert dev_config["catalogMappings"] == {"analytics": "dev_analytics"} + + prod_config = storage.get_environment_config(project, "prod") + assert prod_config["topLevelName"] == "prod_test" + assert prod_config["requireSnapshot"] is True + + def test_v4_migration_preserves_catalog_mappings(self, tmp_path): + """Catalog mappings should survive migration.""" + self._write_v4_project(tmp_path) + project = storage.read_project(tmp_path) + target = storage.get_target_config(project) + dev_env = target["environments"]["dev"] + assert dev_env["catalogMappings"] == {"analytics": "dev_analytics"} + + def test_v5_project_reads_without_migration(self, tmp_path): + """A native v5 project should load directly without changes.""" + storage.ensure_project_file(tmp_path, "unity") + project = storage.read_project(tmp_path) + assert project["version"] == 5 + assert "targets" in project + + def test_ensure_project_file_creates_v5(self, tmp_path): + """New projects should be v5.""" + storage.ensure_project_file(tmp_path, "unity") + + project_path = storage.get_project_file_path(tmp_path) + with open(project_path, encoding="utf-8") as f: + raw = json.load(f) + + assert raw["version"] == 5 + assert "targets" in raw + assert raw["defaultTarget"] == "default" + assert "default" in raw["targets"] + assert raw["targets"]["default"]["type"] == "unity" + + def test_ensure_project_file_auto_migrates_existing_v4(self, tmp_path): + """ensure_project_file should auto-migrate existing v4 projects.""" + self._write_v4_project(tmp_path) + storage.ensure_project_file(tmp_path, "unity") + + project = storage.read_project(tmp_path) + assert project["version"] == 5 + + def test_unsupported_version_raises(self, tmp_path): + """Should raise ValueError for unsupported versions.""" + storage.ensure_schemax_dir(tmp_path) + project_path = storage.get_project_file_path(tmp_path) + with open(project_path, "w", encoding="utf-8") as f: + json.dump({"version": 3, "name": "old"}, f) + + with pytest.raises(ValueError, match="not supported"): + storage.read_project(tmp_path) + + +class TestV5TargetConfig: + """Test target configuration helpers for v5 projects.""" + + def test_get_target_config_default(self, tmp_path): + """get_target_config should return default target when name is None.""" + storage.ensure_project_file(tmp_path, "unity") + project = storage.read_project(tmp_path) + + target = storage.get_target_config(project) + assert target["type"] == "unity" + assert "environments" in target + + def test_get_target_config_by_name(self, tmp_path): + """get_target_config should return named target.""" + storage.ensure_project_file(tmp_path, "unity") + project = storage.read_project(tmp_path) + + # Add a second target + project["targets"]["analytics"] = { + "type": "unity", + "version": "1.0.0", + "environments": { + "dev": { + "topLevelName": "dev_analytics", + "allowDrift": True, + "requireSnapshot": False, + "autoCreateTopLevel": True, + "autoCreateSchemaxSchema": True, + "catalogMappings": {}, + }, + }, + } + storage.write_project(tmp_path, project) + project = storage.read_project(tmp_path) + + target = storage.get_target_config(project, "analytics") + assert target["environments"]["dev"]["topLevelName"] == "dev_analytics" + + def test_get_target_config_not_found(self, tmp_path): + """get_target_config should raise ValueError for missing target.""" + storage.ensure_project_file(tmp_path, "unity") + project = storage.read_project(tmp_path) + + with pytest.raises(ValueError, match="Target 'nonexistent' not found"): + storage.get_target_config(project, "nonexistent") + + def test_get_environment_config_with_target(self, tmp_path): + """get_environment_config should accept scope parameter.""" + storage.ensure_project_file(tmp_path, "unity") + project = storage.read_project(tmp_path) + + # Default target + dev_config = storage.get_environment_config(project, "dev") + assert "topLevelName" in dev_config + + # Explicit target name + dev_config2 = storage.get_environment_config(project, "dev", scope="default") + assert dev_config2["topLevelName"] == dev_config["topLevelName"] + + +class TestV5OperationScope: + """Test scope field on Operation.""" + + def test_operation_scope_default_none(self): + """Operation scope should default to None.""" + op = Operation( + id="op_1", + ts="2024-01-01T00:00:00Z", + provider="unity", + op="unity.add_catalog", + target="cat_1", + payload={"name": "test"}, + ) + assert op.scope is None + + def test_operation_scope_set(self): + """Operation scope should be settable.""" + op = Operation( + id="op_1", + ts="2024-01-01T00:00:00Z", + provider="unity", + op="unity.add_catalog", + target="cat_1", + payload={"name": "test"}, + scope="analytics", + ) + assert op.scope == "analytics" + + def test_create_operation_with_scope(self): + """create_operation should accept scope.""" + op = create_operation( + provider="unity", + op_type="add_catalog", + target="cat_1", + payload={"name": "test"}, + scope="raw", + ) + assert op.scope == "raw" + + def test_create_operation_without_scope(self): + """create_operation without scope should default to None.""" + op = create_operation( + provider="unity", + op_type="add_catalog", + target="cat_1", + payload={"name": "test"}, + ) + assert op.scope is None + + def test_operation_serialization_includes_scope(self): + """scope should survive serialization round-trip.""" + op = Operation( + id="op_1", + ts="2024-01-01T00:00:00Z", + provider="unity", + op="unity.add_catalog", + target="cat_1", + payload={"name": "test"}, + scope="analytics", + ) + dumped = op.model_dump(by_alias=True) + assert dumped["scope"] == "analytics" + + restored = Operation(**dumped) + assert restored.scope == "analytics" + + def test_operation_serialization_null_scope(self): + """Null scope should round-trip cleanly.""" + op = Operation( + id="op_1", + ts="2024-01-01T00:00:00Z", + provider="unity", + op="unity.add_catalog", + target="cat_1", + payload={"name": "test"}, + ) + dumped = op.model_dump(by_alias=True) + assert dumped["scope"] is None + + restored = Operation(**dumped) + assert restored.scope is None + + +class TestV5LoadCurrentState: + """Test load_current_state with v5 target support.""" + + def test_load_current_state_default_target(self, tmp_path): + """load_current_state should work with default target.""" + storage.ensure_project_file(tmp_path, "unity") + state, changelog, provider, _ = storage.load_current_state(tmp_path) + assert provider is not None + assert provider.info.id == "unity" + + def test_load_current_state_explicit_scope(self, tmp_path): + """load_current_state should accept scope.""" + storage.ensure_project_file(tmp_path, "unity") + state, changelog, provider, _ = storage.load_current_state(tmp_path, scope="default") + assert provider.info.id == "unity" + + def test_load_current_state_invalid_scope(self, tmp_path): + """load_current_state should raise for invalid scope.""" + storage.ensure_project_file(tmp_path, "unity") + with pytest.raises(ValueError, match="Target 'nonexistent' not found"): + storage.load_current_state(tmp_path, scope="nonexistent") diff --git a/packages/python-sdk/tests/unit/test_workspace_repository.py b/packages/python-sdk/tests/unit/test_workspace_repository.py index ca38de5..e3869c7 100644 --- a/packages/python-sdk/tests/unit/test_workspace_repository.py +++ b/packages/python-sdk/tests/unit/test_workspace_repository.py @@ -15,8 +15,8 @@ def test_workspace_repository_initializes_workspace(tmp_path: Path) -> None: repo.ensure_initialized(workspace=workspace, provider_id="unity") project = repo.read_project(workspace=workspace) - assert project["version"] == 4 - assert project["provider"]["type"] == "unity" + assert project["version"] == 5 + assert project["targets"]["default"]["type"] == "unity" def test_workspace_repository_appends_operations(tmp_path: Path) -> None: diff --git a/packages/vscode-extension/CHANGELOG.md b/packages/vscode-extension/CHANGELOG.md index d2e0e07..0df9f29 100644 --- a/packages/vscode-extension/CHANGELOG.md +++ b/packages/vscode-extension/CHANGELOG.md @@ -1,5 +1,24 @@ # Changelog +## [0.2.11] - 2026-03-11 + +### Added + +- **Multi-target project support** — Extension reads v5 project schema with `targets` dict. The webview receives per-target configuration and renders target-specific settings. +- **Provider-specific settings panels** — Project Settings now shows target tabs with provider-specific configuration. Unity Catalog settings (environments, catalog mappings, tracking catalog, deployment scope) are rendered by a dedicated `UnityTargetSettings` component. +- **Target tab UI** — New tabbed interface in Project Settings for switching between targets in multi-target projects. Single-target projects display identically to before. + +### Changed + +- **`Operation.scope` field** — Operations use `scope` (renamed from `target_name`) to associate with a specific target. +- **Runtime compatibility floor** — Updated minimum supported CLI version to `0.2.11`. +- **Project creation** — New projects emit v5 schema with `targets` dict directly. + +### Fixed + +- **Extension data pipeline for v5** — All `project.provider` references in `extension.ts` updated to `getTargetConfig(project)` for v5 compatibility (6+ locations). +- **Environment summary** — `EnvironmentSummary` component reads environments from `getDefaultTargetConfig()` instead of `project.provider.environments`. + ## [0.2.10] - 2026-03-10 ### Added diff --git a/packages/vscode-extension/TESTING-UI.md b/packages/vscode-extension/TESTING-UI.md deleted file mode 100644 index ddad2d7..0000000 --- a/packages/vscode-extension/TESTING-UI.md +++ /dev/null @@ -1,74 +0,0 @@ -# UI Testing Guide (VS Code Extension) - -This document describes the testing strategy for the Schemax VS Code extension UI: **unit tests**, **integration tests**, and **E2E tests**. - -## Overview - -| Layer | Scope | Tools | Location | -|-------|--------|-------|----------| -| **Unit** | Pure utils, single components with mocked store | Jest + React Testing Library | `tests/webview/utils/*.test.ts`, `tests/webview/components/*.test.tsx` | -| **Unit (node)** | Storage, providers, no DOM | Jest (node env) | `tests/unit/**/*.test.ts`, `tests/providers/**/*.test.ts` | -| **Integration** | App shell, selection flow | Jest + RTL, mocked store | `tests/webview/integration/*.test.tsx` | -| **E2E** | Full VS Code + webview | @vscode/test-e2e or Playwright | `tests/e2e/` (see tests/e2e/README.md) | - -## Running Tests - -```bash -cd packages/vscode-extension -npm test -npm run test:watch -npm run test:coverage -``` - -Jest uses two projects: **node** (unit/providers) and **jsdom** (webview). - -## Unit Tests - -- **Utils**: e.g. `parsePrivileges` in `tests/webview/utils/grants.test.ts`. -- **Components**: Mock `useDesignerStore`; assert DOM and key interactions (VolumeDetails, FunctionDetails, MaterializedViewDetails, Sidebar, ColumnGrid, TableConstraints, ImportAssetsPanel). - -## Integration Tests - -- **App**: Renders without crashing; shows "No selection" when nothing selected; content area layout (`tests/webview/integration/App.integration.test.tsx`). - -## E2E Tests - -E2E is optional and not yet configured. See `tests/e2e/README.md` for how to add @vscode/test-e2e or Playwright and suggested cases (open Designer, selection flow, add object). - -### E2E: UI → CLI → SDK → live environment - -Two complementary E2E approaches validate the full pipeline: - -#### 1. Jest E2E: UI (store) → capture ops → write workspace → run CLI → live - -**Location:** `tests/webview/integration/App.e2e-ui-to-live.test.tsx` - -- Renders the **real Designer App** and injects `project-loaded` (empty state + provider). -- Drives the **same Zustand store** the UI uses: `addCatalog`, `addSchema`, `addTable`, `addColumn`, `addFunction` (same code path as user clicks). -- Captures `append-ops` messages via the mocked VS Code API and writes `.schemax/project.json` + `changelog.json` to a temp workspace. -- When `SCHEMAX_RUN_LIVE_COMMAND_TESTS=1` and `SCHEMAX_E2E_PROFILE` / `SCHEMAX_E2E_WAREHOUSE_ID` (or Databricks env) are set, spawns `schemax apply` against that workspace and asserts exit code 0. - -So this test validates **UI components (store) → generated state → CLI → live env** using the actual Designer code path. - -#### 2. Python SDK live tests: UI-equivalent state → CLI → SDK → live - -**Location:** `packages/python-sdk/tests/integration/test_e2e_ui_to_live.py` and other `test_live_*.py` files. - -- Build the **same .schemax state** the UI would produce (same op shapes via `OperationBuilder`, `append_ops`) and run `schemax apply` / rollback against a live workspace. -- Validate **UI-equivalent state → CLI → SDK → live** without driving the webview. - -**Running:** - -- **Jest E2E (with live apply):** Set `SCHEMAX_RUN_LIVE_COMMAND_TESTS=1`, `SCHEMAX_E2E_PROFILE`, `SCHEMAX_E2E_WAREHOUSE_ID` (or Databricks env), then `npm test -- App.e2e-ui-to-live` from `packages/vscode-extension`. -- **Interactive prompt:** Run `npm run test:integration` from `packages/vscode-extension`. If the required env vars are already set (e.g. `export SCHEMAX_RUN_LIVE_COMMAND_TESTS=1` or a `.env`), prompts are skipped. When any are missing and stdin is a TTY, the script prompts for each and then runs the E2E test. In CI or non-interactive runs, the test runs without live apply (same as `npm test`). -- **Python live tests:** See `packages/python-sdk/tests/integration/README_LIVE_TESTS.md` (requires `SCHEMAX_RUN_LIVE_COMMAND_TESTS=1` and Databricks config). - -## Adding New Tests - -- New util: `tests/webview/utils/.test.ts` or `tests/unit/`. -- New component: `tests/webview/components/.test.tsx` with mocked store. -- New integration: `tests/webview/integration/.integration.test.tsx`. - -## Coverage - -Run `npm run test:coverage`; thresholds are in `jest.config.js`. Report: `coverage/lcov-report/index.html`. diff --git a/packages/vscode-extension/package-lock.json b/packages/vscode-extension/package-lock.json new file mode 100644 index 0000000..863a49f --- /dev/null +++ b/packages/vscode-extension/package-lock.json @@ -0,0 +1,11332 @@ +{ + "name": "schemax-vscode", + "version": "0.2.11", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "schemax-vscode", + "version": "0.2.11", + "license": "Apache-2.0", + "dependencies": { + "@vscode/codicons": "^0.0.36", + "@vscode/python-extension": "^1.0.6", + "@vscode/webview-ui-toolkit": "^1.4.0", + "react": "^18.2.0", + "react-dom": "^18.2.0", + "uuid": "^9.0.1", + "zod": "^3.23.0", + "zustand": "^4.5.0" + }, + "devDependencies": { + "@jest/globals": "^29.7.0", + "@testing-library/jest-dom": "^6.9.1", + "@testing-library/react": "^16.3.0", + "@testing-library/user-event": "^14.6.1", + "@types/jest": "^29.5.0", + "@types/node": "^20.11.0", + "@types/react": "^18.2.0", + "@types/react-dom": "^18.2.0", + "@types/uuid": "^9.0.0", + "@types/vscode": "^1.90.0", + "@typescript-eslint/eslint-plugin": "^8.46.1", + "@typescript-eslint/parser": "^8.46.1", + "@vitejs/plugin-react": "^4.2.0", + "@vscode/vsce": "^3.6.0", + "concurrently": "^8.2.0", + "esbuild": "^0.27.0", + "eslint": "^9.38.0", + "eslint-config-prettier": "^10.1.8", + "jest": "^29.7.0", + "jest-environment-jsdom": "^29.7.0", + "prettier": "^3.6.2", + "ts-jest": "^29.4.5", + "typescript": "^5.4.0", + "vite": "^7.0.0" + }, + "engines": { + "vscode": "^1.90.0" + }, + "optionalDependencies": { + "@rollup/rollup-darwin-arm64": "^4.52.0", + "@rollup/rollup-linux-x64-gnu": "^4.52.0" + } + }, + "node_modules/@adobe/css-tools": { + "version": "4.4.4", + "resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.4.4.tgz", + "integrity": "sha512-Elp+iwUx5rN5+Y8xLt5/GRoG20WGoDCQ/1Fb+1LiGtvwbDavuSk0jhD/eZdckHAuzcDzccnkv+rEjyWfRx18gg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@azu/format-text": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@azu/format-text/-/format-text-1.0.2.tgz", + "integrity": "sha512-Swi4N7Edy1Eqq82GxgEECXSSLyn6GOb5htRFPzBDdUkECGXtlf12ynO5oJSpWKPwCaUssOu7NfhDcCWpIC6Ywg==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@azu/style-format": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@azu/style-format/-/style-format-1.0.1.tgz", + "integrity": "sha512-AHcTojlNBdD/3/KxIKlg8sxIWHfOtQszLvOpagLTO+bjC3u7SAszu1lf//u7JJC50aUSH+BVWDD/KvaA6Gfn5g==", + "dev": true, + "license": "WTFPL", + "dependencies": { + "@azu/format-text": "^1.0.1" + } + }, + "node_modules/@azure/abort-controller": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@azure/abort-controller/-/abort-controller-2.1.2.tgz", + "integrity": "sha512-nBrLsEWm4J2u5LpAPjxADTlq3trDgVZZXHNKabeXZtpq3d3AbN/KGO82R87rdDz5/lYB024rtEf10/q0urNgsA==", + "dev": true, + "license": "MIT", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@azure/core-auth": { + "version": "1.10.1", + "resolved": "https://registry.npmjs.org/@azure/core-auth/-/core-auth-1.10.1.tgz", + "integrity": "sha512-ykRMW8PjVAn+RS6ww5cmK9U2CyH9p4Q88YJwvUslfuMmN98w/2rdGRLPqJYObapBCdzBVeDgYWdJnFPFb7qzpg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@azure/abort-controller": "^2.1.2", + "@azure/core-util": "^1.13.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@azure/core-client": { + "version": "1.10.1", + "resolved": "https://registry.npmjs.org/@azure/core-client/-/core-client-1.10.1.tgz", + "integrity": "sha512-Nh5PhEOeY6PrnxNPsEHRr9eimxLwgLlpmguQaHKBinFYA/RU9+kOYVOQqOrTsCL+KSxrLLl1gD8Dk5BFW/7l/w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@azure/abort-controller": "^2.1.2", + "@azure/core-auth": "^1.10.0", + "@azure/core-rest-pipeline": "^1.22.0", + "@azure/core-tracing": "^1.3.0", + "@azure/core-util": "^1.13.0", + "@azure/logger": "^1.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@azure/core-rest-pipeline": { + "version": "1.23.0", + "resolved": "https://registry.npmjs.org/@azure/core-rest-pipeline/-/core-rest-pipeline-1.23.0.tgz", + "integrity": "sha512-Evs1INHo+jUjwHi1T6SG6Ua/LHOQBCLuKEEE6efIpt4ZOoNonaT1kP32GoOcdNDbfqsD2445CPri3MubBy5DEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@azure/abort-controller": "^2.1.2", + "@azure/core-auth": "^1.10.0", + "@azure/core-tracing": "^1.3.0", + "@azure/core-util": "^1.13.0", + "@azure/logger": "^1.3.0", + "@typespec/ts-http-runtime": "^0.3.4", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@azure/core-tracing": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@azure/core-tracing/-/core-tracing-1.3.1.tgz", + "integrity": "sha512-9MWKevR7Hz8kNzzPLfX4EAtGM2b8mr50HPDBvio96bURP/9C+HjdH3sBlLSNNrvRAr5/k/svoH457gB5IKpmwQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@azure/core-util": { + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/@azure/core-util/-/core-util-1.13.1.tgz", + "integrity": "sha512-XPArKLzsvl0Hf0CaGyKHUyVgF7oDnhKoP85Xv6M4StF/1AhfORhZudHtOyf2s+FcbuQ9dPRAjB8J2KvRRMUK2A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@azure/abort-controller": "^2.1.2", + "@typespec/ts-http-runtime": "^0.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@azure/identity": { + "version": "4.13.0", + "resolved": "https://registry.npmjs.org/@azure/identity/-/identity-4.13.0.tgz", + "integrity": "sha512-uWC0fssc+hs1TGGVkkghiaFkkS7NkTxfnCH+Hdg+yTehTpMcehpok4PgUKKdyCH+9ldu6FhiHRv84Ntqj1vVcw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@azure/abort-controller": "^2.0.0", + "@azure/core-auth": "^1.9.0", + "@azure/core-client": "^1.9.2", + "@azure/core-rest-pipeline": "^1.17.0", + "@azure/core-tracing": "^1.0.0", + "@azure/core-util": "^1.11.0", + "@azure/logger": "^1.0.0", + "@azure/msal-browser": "^4.2.0", + "@azure/msal-node": "^3.5.0", + "open": "^10.1.0", + "tslib": "^2.2.0" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@azure/logger": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@azure/logger/-/logger-1.3.0.tgz", + "integrity": "sha512-fCqPIfOcLE+CGqGPd66c8bZpwAji98tZ4JI9i/mlTNTlsIWslCfpg48s/ypyLxZTump5sypjrKn2/kY7q8oAbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typespec/ts-http-runtime": "^0.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@azure/msal-browser": { + "version": "4.29.0", + "resolved": "https://registry.npmjs.org/@azure/msal-browser/-/msal-browser-4.29.0.tgz", + "integrity": "sha512-/f3eHkSNUTl6DLQHm+bKecjBKcRQxbd/XLx8lvSYp8Nl/HRyPuIPOijt9Dt0sH50/SxOwQ62RnFCmFlGK+bR/w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@azure/msal-common": "15.15.0" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/@azure/msal-common": { + "version": "15.15.0", + "resolved": "https://registry.npmjs.org/@azure/msal-common/-/msal-common-15.15.0.tgz", + "integrity": "sha512-/n+bN0AKlVa+AOcETkJSKj38+bvFs78BaP4rNtv3MJCmPH0YrHiskMRe74OhyZ5DZjGISlFyxqvf9/4QVEi2tw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/@azure/msal-node": { + "version": "3.8.8", + "resolved": "https://registry.npmjs.org/@azure/msal-node/-/msal-node-3.8.8.tgz", + "integrity": "sha512-+f1VrJH1iI517t4zgmuhqORja0bL6LDQXfBqkjuMmfTYXTQQnh1EvwwxO3UbKLT05N0obF72SRHFrC1RBDv5Gg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@azure/msal-common": "15.15.0", + "jsonwebtoken": "^9.0.0", + "uuid": "^8.3.0" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/@azure/msal-node/node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "dev": true, + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz", + "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.28.5", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.0.tgz", + "integrity": "sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.0.tgz", + "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.0", + "@babel/generator": "^7.29.0", + "@babel/helper-compilation-targets": "^7.28.6", + "@babel/helper-module-transforms": "^7.28.6", + "@babel/helpers": "^7.28.6", + "@babel/parser": "^7.29.0", + "@babel/template": "^7.28.6", + "@babel/traverse": "^7.29.0", + "@babel/types": "^7.29.0", + "@jridgewell/remapping": "^2.3.5", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/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.29.1", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.1.tgz", + "integrity": "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.0", + "@babel/types": "^7.29.0", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz", + "integrity": "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.28.6", + "@babel/helper-validator-option": "^7.27.1", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-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.29.0", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.0.tgz", + "integrity": "sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.0" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-syntax-async-generators": { + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", + "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-bigint": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz", + "integrity": "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-class-properties": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", + "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.12.13" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-class-static-block": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz", + "integrity": "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-attributes": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.28.6.tgz", + "integrity": "sha512-jiLC0ma9XkQT3TKJ9uYvlakm66Pamywo+qwL+oL8HJOvc6TWdZXVfhqJr8CCzbSGUAbDOzlGHJC1U+vRfLQDvw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-meta": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", + "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-json-strings": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", + "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-jsx": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.28.6.tgz", + "integrity": "sha512-wgEmr06G6sIpqr8YDwA2dSRTE3bJ+V0IfpzfSY3Lfgd7YWOaAdlykvJi13ZKBt8cZHfgH1IXN+CL656W3uUa4w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-logical-assignment-operators": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", + "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-nullish-coalescing-operator": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", + "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-numeric-separator": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", + "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-object-rest-spread": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", + "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-optional-catch-binding": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", + "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-optional-chaining": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", + "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-private-property-in-object": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz", + "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-top-level-await": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", + "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-typescript": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.28.6.tgz", + "integrity": "sha512-+nDNmQye7nlnuuHDboPbGm00Vqg3oO8niRRL27/4LYHUsHYh0zJ1xWOz0uRwNFmM1Avzk8wZbc6rdiYhomzv/A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.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==", + "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": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.6.tgz", + "integrity": "sha512-05WQkdpL9COIMz4LjTxGpPNCdlpyimKppYNoJ5Di5EUObifl8t4tuLuUBBZEpoLYOmfvIWrsp9fCl0HoPRVTdA==", + "dev": true, + "license": "MIT", + "engines": { + "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.29.0", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.0.tgz", + "integrity": "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.0", + "@babel/generator": "^7.29.0", + "@babel/helper-globals": "^7.28.0", + "@babel/parser": "^7.29.0", + "@babel/template": "^7.28.6", + "@babel/types": "^7.29.0", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", + "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@bcoe/v8-coverage": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", + "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.3.tgz", + "integrity": "sha512-9fJMTNFTWZMh5qwrBItuziu834eOCUcEqymSH7pY+zoMVEZg3gcPuBNxH1EvfVYe9h0x/Ptw8KBzv7qxb7l8dg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.3.tgz", + "integrity": "sha512-i5D1hPY7GIQmXlXhs2w8AWHhenb00+GxjxRncS2ZM7YNVGNfaMxgzSGuO8o8SJzRc/oZwU2bcScvVERk03QhzA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.3.tgz", + "integrity": "sha512-YdghPYUmj/FX2SYKJ0OZxf+iaKgMsKHVPF1MAq/P8WirnSpCStzKJFjOjzsW0QQ7oIAiccHdcqjbHmJxRb/dmg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.3.tgz", + "integrity": "sha512-IN/0BNTkHtk8lkOM8JWAYFg4ORxBkZQf9zXiEOfERX/CzxW3Vg1ewAhU7QSWQpVIzTW+b8Xy+lGzdYXV6UZObQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.3.tgz", + "integrity": "sha512-Re491k7ByTVRy0t3EKWajdLIr0gz2kKKfzafkth4Q8A5n1xTHrkqZgLLjFEHVD+AXdUGgQMq+Godfq45mGpCKg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.3.tgz", + "integrity": "sha512-vHk/hA7/1AckjGzRqi6wbo+jaShzRowYip6rt6q7VYEDX4LEy1pZfDpdxCBnGtl+A5zq8iXDcyuxwtv3hNtHFg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.3.tgz", + "integrity": "sha512-ipTYM2fjt3kQAYOvo6vcxJx3nBYAzPjgTCk7QEgZG8AUO3ydUhvelmhrbOheMnGOlaSFUoHXB6un+A7q4ygY9w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.3.tgz", + "integrity": "sha512-dDk0X87T7mI6U3K9VjWtHOXqwAMJBNN2r7bejDsc+j03SEjtD9HrOl8gVFByeM0aJksoUuUVU9TBaZa2rgj0oA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.3.tgz", + "integrity": "sha512-s6nPv2QkSupJwLYyfS+gwdirm0ukyTFNl3KTgZEAiJDd+iHZcbTPPcWCcRYH+WlNbwChgH2QkE9NSlNrMT8Gfw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.3.tgz", + "integrity": "sha512-sZOuFz/xWnZ4KH3YfFrKCf1WyPZHakVzTiqji3WDc0BCl2kBwiJLCXpzLzUBLgmp4veFZdvN5ChW4Eq/8Fc2Fg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.3.tgz", + "integrity": "sha512-yGlQYjdxtLdh0a3jHjuwOrxQjOZYD/C9PfdbgJJF3TIZWnm/tMd/RcNiLngiu4iwcBAOezdnSLAwQDPqTmtTYg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.3.tgz", + "integrity": "sha512-WO60Sn8ly3gtzhyjATDgieJNet/KqsDlX5nRC5Y3oTFcS1l0KWba+SEa9Ja1GfDqSF1z6hif/SkpQJbL63cgOA==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.3.tgz", + "integrity": "sha512-APsymYA6sGcZ4pD6k+UxbDjOFSvPWyZhjaiPyl/f79xKxwTnrn5QUnXR5prvetuaSMsb4jgeHewIDCIWljrSxw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.3.tgz", + "integrity": "sha512-eizBnTeBefojtDb9nSh4vvVQ3V9Qf9Df01PfawPcRzJH4gFSgrObw+LveUyDoKU3kxi5+9RJTCWlj4FjYXVPEA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.3.tgz", + "integrity": "sha512-3Emwh0r5wmfm3ssTWRQSyVhbOHvqegUDRd0WhmXKX2mkHJe1SFCMJhagUleMq+Uci34wLSipf8Lagt4LlpRFWQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.3.tgz", + "integrity": "sha512-pBHUx9LzXWBc7MFIEEL0yD/ZVtNgLytvx60gES28GcWMqil8ElCYR4kvbV2BDqsHOvVDRrOxGySBM9Fcv744hw==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.3.tgz", + "integrity": "sha512-Czi8yzXUWIQYAtL/2y6vogER8pvcsOsk5cpwL4Gk5nJqH5UZiVByIY8Eorm5R13gq+DQKYg0+JyQoytLQas4dA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.3.tgz", + "integrity": "sha512-sDpk0RgmTCR/5HguIZa9n9u+HVKf40fbEUt+iTzSnCaGvY9kFP0YKBWZtJaraonFnqef5SlJ8/TiPAxzyS+UoA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.3.tgz", + "integrity": "sha512-P14lFKJl/DdaE00LItAukUdZO5iqNH7+PjoBm+fLQjtxfcfFE20Xf5CrLsmZdq5LFFZzb5JMZ9grUwvtVYzjiA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.3.tgz", + "integrity": "sha512-AIcMP77AvirGbRl/UZFTq5hjXK+2wC7qFRGoHSDrZ5v5b8DK/GYpXW3CPRL53NkvDqb9D+alBiC/dV0Fb7eJcw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.3.tgz", + "integrity": "sha512-DnW2sRrBzA+YnE70LKqnM3P+z8vehfJWHXECbwBmH/CU51z6FiqTQTHFenPlHmo3a8UgpLyH3PT+87OViOh1AQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.3.tgz", + "integrity": "sha512-NinAEgr/etERPTsZJ7aEZQvvg/A6IsZG/LgZy+81wON2huV7SrK3e63dU0XhyZP4RKGyTm7aOgmQk0bGp0fy2g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.3.tgz", + "integrity": "sha512-PanZ+nEz+eWoBJ8/f8HKxTTD172SKwdXebZ0ndd953gt1HRBbhMsaNqjTyYLGLPdoWHy4zLU7bDVJztF5f3BHA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.3.tgz", + "integrity": "sha512-B2t59lWWYrbRDw/tjiWOuzSsFh1Y/E95ofKz7rIVYSQkUYBjfSgf6oeYPNWHToFRr2zx52JKApIcAS/D5TUBnA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.3.tgz", + "integrity": "sha512-QLKSFeXNS8+tHW7tZpMtjlNb7HKau0QDpwm49u0vUp9y1WOF+PEzkU84y9GqYaAVW8aH8f3GcBck26jh54cX4Q==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.3.tgz", + "integrity": "sha512-4uJGhsxuptu3OcpVAzli+/gWusVGwZZHTlS63hh++ehExkVT8SgiEf7/uC/PclrPPkLhZqGgCTjd0VWLo6xMqA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "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", + "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", + "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/config-array": { + "version": "0.21.2", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.2.tgz", + "integrity": "sha512-nJl2KGTlrf9GjLimgIru+V/mzgSK0ABCDQRvxw5BjURL7WfH5uoWmizbH7QB6MmnMBd8cIC9uceWnezL1VZWWw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/object-schema": "^2.1.7", + "debug": "^4.3.1", + "minimatch": "^3.1.5" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/config-array/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@eslint/config-array/node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@eslint/config-array/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@eslint/config-helpers": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.4.2.tgz", + "integrity": "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.17.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/core": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.17.0.tgz", + "integrity": "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "3.3.5", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.5.tgz", + "integrity": "sha512-4IlJx0X0qftVsN5E+/vGujTRIFtwuLbNsVUe7TO6zYPDR1O6nFwvwhIKEKSrl6dZchmYBITazxKoUYOjdtjlRg==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^6.14.0", + "debug": "^4.3.2", + "espree": "^10.0.1", + "globals": "^14.0.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.1", + "minimatch": "^3.1.5", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/eslintrc/node_modules/ajv": { + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz", + "integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/@eslint/eslintrc/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@eslint/eslintrc/node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@eslint/eslintrc/node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/@eslint/eslintrc/node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@eslint/eslintrc/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@eslint/js": { + "version": "9.39.4", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.4.tgz", + "integrity": "sha512-nE7DEIchvtiFTwBw4Lfbu59PG+kCofhjsKaCWzxTpt4lfRjRMqG6uMBzKXuEcyXhOHoUp9riAm7/aWYGhXZ9cw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + } + }, + "node_modules/@eslint/object-schema": { + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.7.tgz", + "integrity": "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/plugin-kit": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.4.1.tgz", + "integrity": "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.17.0", + "levn": "^0.4.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@humanfs/core": { + "version": "0.19.1", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", + "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node": { + "version": "0.16.7", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.7.tgz", + "integrity": "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/core": "^0.19.1", + "@humanwhocodes/retry": "^0.4.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/retry": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", + "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@isaacs/cliui": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-9.0.0.tgz", + "integrity": "sha512-AokJm4tuBHillT+FpMtxQ60n8ObyXBatq7jD2/JA9dxbDDokKQm8KMht5ibGzLVU9IJDIKK4TPKgMHEYMn3lMg==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/@istanbuljs/load-nyc-config": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", + "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "camelcase": "^5.3.1", + "find-up": "^4.1.0", + "get-package-type": "^0.1.0", + "js-yaml": "^3.13.1", + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "license": "MIT", + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/js-yaml": { + "version": "3.14.2", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.2.tgz", + "integrity": "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/schema": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", + "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/console": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/console/-/console-29.7.0.tgz", + "integrity": "sha512-5Ni4CU7XHQi32IJ398EEP4RrB8eV09sXP2ROqD4bksHrnTree52PsxvX8tpL8LvTZ3pFzXyPbNQReSN41CAhOg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/core": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/core/-/core-29.7.0.tgz", + "integrity": "sha512-n7aeXWKMnGtDA48y8TLWJPJmLmmZ642Ceo78cYWEpiD7FzDgmNDV/GCVRorPABdXLJZ/9wzzgZAlHjXjxDHGsg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/console": "^29.7.0", + "@jest/reporters": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "ansi-escapes": "^4.2.1", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "exit": "^0.1.2", + "graceful-fs": "^4.2.9", + "jest-changed-files": "^29.7.0", + "jest-config": "^29.7.0", + "jest-haste-map": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-resolve": "^29.7.0", + "jest-resolve-dependencies": "^29.7.0", + "jest-runner": "^29.7.0", + "jest-runtime": "^29.7.0", + "jest-snapshot": "^29.7.0", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "jest-watcher": "^29.7.0", + "micromatch": "^4.0.4", + "pretty-format": "^29.7.0", + "slash": "^3.0.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/@jest/core/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/@jest/core/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/@jest/core/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/@jest/core/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/environment": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-29.7.0.tgz", + "integrity": "sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/fake-timers": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "jest-mock": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/expect": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-29.7.0.tgz", + "integrity": "sha512-8uMeAMycttpva3P1lBHB8VciS9V0XAr3GymPpipdyQXbBcuhkLQOSe8E/p92RyAdToS6ZD1tFkX+CkhoECE0dQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "expect": "^29.7.0", + "jest-snapshot": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/expect-utils": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-29.7.0.tgz", + "integrity": "sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "jest-get-type": "^29.6.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/fake-timers": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-29.7.0.tgz", + "integrity": "sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "@sinonjs/fake-timers": "^10.0.2", + "@types/node": "*", + "jest-message-util": "^29.7.0", + "jest-mock": "^29.7.0", + "jest-util": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/globals": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-29.7.0.tgz", + "integrity": "sha512-mpiz3dutLbkW2MNFubUGUEVLkTGiqW6yLVTA+JbP6fI6J5iL9Y0Nlg8k95pcF8ctKwCS7WVxteBs29hhfAotzQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/expect": "^29.7.0", + "@jest/types": "^29.6.3", + "jest-mock": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/reporters": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-29.7.0.tgz", + "integrity": "sha512-DApq0KJbJOEzAFYjHADNNxAE3KbhxQB1y5Kplb5Waqw6zVbuWatSnMjE5gs8FUgEPmNsnZA3NCWl9NG0ia04Pg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@bcoe/v8-coverage": "^0.2.3", + "@jest/console": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@jridgewell/trace-mapping": "^0.3.18", + "@types/node": "*", + "chalk": "^4.0.0", + "collect-v8-coverage": "^1.0.0", + "exit": "^0.1.2", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "istanbul-lib-coverage": "^3.0.0", + "istanbul-lib-instrument": "^6.0.0", + "istanbul-lib-report": "^3.0.0", + "istanbul-lib-source-maps": "^4.0.0", + "istanbul-reports": "^3.1.3", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0", + "jest-worker": "^29.7.0", + "slash": "^3.0.0", + "string-length": "^4.0.1", + "strip-ansi": "^6.0.0", + "v8-to-istanbul": "^9.0.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/@jest/reporters/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jest/reporters/node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@jest/reporters/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "dev": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@jest/reporters/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@jest/reporters/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@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/@jest/source-map": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-29.6.3.tgz", + "integrity": "sha512-MHjT95QuipcPrpLM+8JMSzFx6eHp5Bm+4XeFDJlwsvVBjmKNiIAvasGK2fxz2WbGRlnvqehFbh07MMa7n3YJnw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.18", + "callsites": "^3.0.0", + "graceful-fs": "^4.2.9" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/test-result": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-29.7.0.tgz", + "integrity": "sha512-Fdx+tv6x1zlkJPcWXmMDAG2HBnaR9XPSd5aDWQVsfrZmLVT3lU1cwyxLgRmXR9yrq4NBoEm9BMsfgFzTQAbJYA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/console": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "collect-v8-coverage": "^1.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/test-sequencer": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-29.7.0.tgz", + "integrity": "sha512-GQwJ5WZVrKnOJuiYiAF52UNUJXgTZx1NHjFSEB0qEMmSZKAkdMoIzw/Cj6x6NF4AvV23AUqDpFzQkN/eYCYTxw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/test-result": "^29.7.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/transform": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.7.0.tgz", + "integrity": "sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.11.6", + "@jest/types": "^29.6.3", + "@jridgewell/trace-mapping": "^0.3.18", + "babel-plugin-istanbul": "^6.1.1", + "chalk": "^4.0.0", + "convert-source-map": "^2.0.0", + "fast-json-stable-stringify": "^2.1.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-util": "^29.7.0", + "micromatch": "^4.0.4", + "pirates": "^4.0.4", + "slash": "^3.0.0", + "write-file-atomic": "^4.0.2" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/types": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", + "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/schemas": "^29.6.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^17.0.8", + "chalk": "^4.0.0" + }, + "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==", + "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/@microsoft/fast-element": { + "version": "1.14.0", + "resolved": "https://registry.npmjs.org/@microsoft/fast-element/-/fast-element-1.14.0.tgz", + "integrity": "sha512-zXvuSOzvsu8zDTy9eby8ix8VqLop2rwKRgp++ZN2kTCsoB3+QJVoaGD2T/Cyso2ViZQFXNpiNCVKfnmxBvmWkQ==", + "license": "MIT" + }, + "node_modules/@microsoft/fast-foundation": { + "version": "2.50.0", + "resolved": "https://registry.npmjs.org/@microsoft/fast-foundation/-/fast-foundation-2.50.0.tgz", + "integrity": "sha512-8mFYG88Xea1jZf2TI9Lm/jzZ6RWR8x29r24mGuLojNYqIR2Bl8+hnswoV6laApKdCbGMPKnsAL/O68Q0sRxeVg==", + "license": "MIT", + "dependencies": { + "@microsoft/fast-element": "^1.14.0", + "@microsoft/fast-web-utilities": "^5.4.1", + "tabbable": "^5.2.0", + "tslib": "^1.13.0" + } + }, + "node_modules/@microsoft/fast-foundation/node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "license": "0BSD" + }, + "node_modules/@microsoft/fast-react-wrapper": { + "version": "0.3.25", + "resolved": "https://registry.npmjs.org/@microsoft/fast-react-wrapper/-/fast-react-wrapper-0.3.25.tgz", + "integrity": "sha512-jKzmk2xJV93RL/jEFXEZgBvXlKIY4N4kXy3qrjmBfFpqNi3VjY+oUTWyMnHRMC5EUhIFxD+Y1VD4u9uIPX3jQw==", + "license": "MIT", + "dependencies": { + "@microsoft/fast-element": "^1.14.0", + "@microsoft/fast-foundation": "^2.50.0" + }, + "peerDependencies": { + "react": ">=16.9.0" + } + }, + "node_modules/@microsoft/fast-web-utilities": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/@microsoft/fast-web-utilities/-/fast-web-utilities-5.4.1.tgz", + "integrity": "sha512-ReWYncndjV3c8D8iq9tp7NcFNc1vbVHvcBFPME2nNFKNbS1XCesYZGlIlf3ot5EmuOXPlrzUHOWzQ2vFpIkqDg==", + "license": "MIT", + "dependencies": { + "exenv-es6": "^1.1.1" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "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.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.59.0.tgz", + "integrity": "sha512-upnNBkA6ZH2VKGcBj9Fyl9IGNPULcjXRlg0LLeaioQWueH30p6IXtJEbKAgvyv+mJaMxSm1l6xwDXYjpEMiLMg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.59.0.tgz", + "integrity": "sha512-hZ+Zxj3SySm4A/DylsDKZAeVg0mvi++0PYVceVyX7hemkw7OreKdCvW2oQ3T1FMZvCaQXqOTHb8qmBShoqk69Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.59.0.tgz", + "integrity": "sha512-W2Psnbh1J8ZJw0xKAd8zdNgF9HRLkdWwwdWqubSVk0pUuQkoHnv7rx4GiF9rT4t5DIZGAsConRE3AxCdJ4m8rg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.59.0.tgz", + "integrity": "sha512-ZW2KkwlS4lwTv7ZVsYDiARfFCnSGhzYPdiOU4IM2fDbL+QGlyAbjgSFuqNRbSthybLbIJ915UtZBtmuLrQAT/w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.59.0.tgz", + "integrity": "sha512-EsKaJ5ytAu9jI3lonzn3BgG8iRBjV4LxZexygcQbpiU0wU0ATxhNVEpXKfUa0pS05gTcSDMKpn3Sx+QB9RlTTA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.59.0.tgz", + "integrity": "sha512-d3DuZi2KzTMjImrxoHIAODUZYoUUMsuUiY4SRRcJy6NJoZ6iIqWnJu9IScV9jXysyGMVuW+KNzZvBLOcpdl3Vg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.59.0.tgz", + "integrity": "sha512-t4ONHboXi/3E0rT6OZl1pKbl2Vgxf9vJfWgmUoCEVQVxhW6Cw/c8I6hbbu7DAvgp82RKiH7TpLwxnJeKv2pbsw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.59.0.tgz", + "integrity": "sha512-CikFT7aYPA2ufMD086cVORBYGHffBo4K8MQ4uPS/ZnY54GKj36i196u8U+aDVT2LX4eSMbyHtyOh7D7Zvk2VvA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.59.0.tgz", + "integrity": "sha512-jYgUGk5aLd1nUb1CtQ8E+t5JhLc9x5WdBKew9ZgAXg7DBk0ZHErLHdXM24rfX+bKrFe+Xp5YuJo54I5HFjGDAA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.59.0.tgz", + "integrity": "sha512-peZRVEdnFWZ5Bh2KeumKG9ty7aCXzzEsHShOZEFiCQlDEepP1dpUl/SrUNXNg13UmZl+gzVDPsiCwnV1uI0RUA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.59.0.tgz", + "integrity": "sha512-gbUSW/97f7+r4gHy3Jlup8zDG190AuodsWnNiXErp9mT90iCy9NKKU0Xwx5k8VlRAIV2uU9CsMnEFg/xXaOfXg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.59.0.tgz", + "integrity": "sha512-yTRONe79E+o0FWFijasoTjtzG9EBedFXJMl888NBEDCDV9I2wGbFFfJQQe63OijbFCUZqxpHz1GzpbtSFikJ4Q==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.59.0.tgz", + "integrity": "sha512-sw1o3tfyk12k3OEpRddF68a1unZ5VCN7zoTNtSn2KndUE+ea3m3ROOKRCZxEpmT9nsGnogpFP9x6mnLTCaoLkA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.59.0.tgz", + "integrity": "sha512-+2kLtQ4xT3AiIxkzFVFXfsmlZiG5FXYW7ZyIIvGA7Bdeuh9Z0aN4hVyXS/G1E9bTP/vqszNIN/pUKCk/BTHsKA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.59.0.tgz", + "integrity": "sha512-NDYMpsXYJJaj+I7UdwIuHHNxXZ/b/N2hR15NyH3m2qAtb/hHPA4g4SuuvrdxetTdndfj9b1WOmy73kcPRoERUg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.59.0.tgz", + "integrity": "sha512-nLckB8WOqHIf1bhymk+oHxvM9D3tyPndZH8i8+35p/1YiVoVswPid2yLzgX7ZJP0KQvnkhM4H6QZ5m0LzbyIAg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.59.0.tgz", + "integrity": "sha512-oF87Ie3uAIvORFBpwnCvUzdeYUqi2wY6jRFWJAy1qus/udHFYIkplYRW+wo+GRUP4sKzYdmE1Y3+rY5Gc4ZO+w==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.59.0.tgz", + "integrity": "sha512-3AHmtQq/ppNuUspKAlvA8HtLybkDflkMuLK4DPo77DfthRb71V84/c4MlWJXixZz4uruIH4uaa07IqoAkG64fg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.59.0.tgz", + "integrity": "sha512-2UdiwS/9cTAx7qIUZB/fWtToJwvt0Vbo0zmnYt7ED35KPg13Q0ym1g442THLC7VyI6JfYTP4PiSOWyoMdV2/xg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.59.0.tgz", + "integrity": "sha512-M3bLRAVk6GOwFlPTIxVBSYKUaqfLrn8l0psKinkCFxl4lQvOSz8ZrKDz2gxcBwHFpci0B6rttydI4IpS4IS/jQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.59.0.tgz", + "integrity": "sha512-tt9KBJqaqp5i5HUZzoafHZX8b5Q2Fe7UjYERADll83O4fGqJ49O1FsL6LpdzVFQcpwvnyd0i+K/VSwu/o/nWlA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.59.0.tgz", + "integrity": "sha512-V5B6mG7OrGTwnxaNUzZTDTjDS7F75PO1ae6MJYdiMu60sq0CqN5CVeVsbhPxalupvTX8gXVSU9gq+Rx1/hvu6A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.59.0.tgz", + "integrity": "sha512-UKFMHPuM9R0iBegwzKF4y0C4J9u8C6MEJgFuXTBerMk7EJ92GFVFYBfOZaSGLu6COf7FxpQNqhNS4c4icUPqxA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.59.0.tgz", + "integrity": "sha512-laBkYlSS1n2L8fSo1thDNGrCTQMmxjYY5G0WFWjFFYZkKPjsMBsgJfGf4TLxXrF6RyhI60L8TMOjBMvXiTcxeA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.59.0.tgz", + "integrity": "sha512-2HRCml6OztYXyJXAvdDXPKcawukWY2GpR5/nxKp4iBgiO3wcoEGkAaqctIbZcNB6KlUQBIqt8VYkNSj2397EfA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@secretlint/config-creator": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/@secretlint/config-creator/-/config-creator-10.2.2.tgz", + "integrity": "sha512-BynOBe7Hn3LJjb3CqCHZjeNB09s/vgf0baBaHVw67w7gHF0d25c3ZsZ5+vv8TgwSchRdUCRrbbcq5i2B1fJ2QQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@secretlint/types": "^10.2.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@secretlint/config-loader": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/@secretlint/config-loader/-/config-loader-10.2.2.tgz", + "integrity": "sha512-ndjjQNgLg4DIcMJp4iaRD6xb9ijWQZVbd9694Ol2IszBIbGPPkwZHzJYKICbTBmh6AH/pLr0CiCaWdGJU7RbpQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@secretlint/profiler": "^10.2.2", + "@secretlint/resolver": "^10.2.2", + "@secretlint/types": "^10.2.2", + "ajv": "^8.17.1", + "debug": "^4.4.1", + "rc-config-loader": "^4.1.3" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@secretlint/core": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/@secretlint/core/-/core-10.2.2.tgz", + "integrity": "sha512-6rdwBwLP9+TO3rRjMVW1tX+lQeo5gBbxl1I5F8nh8bgGtKwdlCMhMKsBWzWg1ostxx/tIG7OjZI0/BxsP8bUgw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@secretlint/profiler": "^10.2.2", + "@secretlint/types": "^10.2.2", + "debug": "^4.4.1", + "structured-source": "^4.0.0" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@secretlint/formatter": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/@secretlint/formatter/-/formatter-10.2.2.tgz", + "integrity": "sha512-10f/eKV+8YdGKNQmoDUD1QnYL7TzhI2kzyx95vsJKbEa8akzLAR5ZrWIZ3LbcMmBLzxlSQMMccRmi05yDQ5YDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@secretlint/resolver": "^10.2.2", + "@secretlint/types": "^10.2.2", + "@textlint/linter-formatter": "^15.2.0", + "@textlint/module-interop": "^15.2.0", + "@textlint/types": "^15.2.0", + "chalk": "^5.4.1", + "debug": "^4.4.1", + "pluralize": "^8.0.0", + "strip-ansi": "^7.1.0", + "table": "^6.9.0", + "terminal-link": "^4.0.0" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@secretlint/formatter/node_modules/chalk": { + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", + "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/@secretlint/node": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/@secretlint/node/-/node-10.2.2.tgz", + "integrity": "sha512-eZGJQgcg/3WRBwX1bRnss7RmHHK/YlP/l7zOQsrjexYt6l+JJa5YhUmHbuGXS94yW0++3YkEJp0kQGYhiw1DMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@secretlint/config-loader": "^10.2.2", + "@secretlint/core": "^10.2.2", + "@secretlint/formatter": "^10.2.2", + "@secretlint/profiler": "^10.2.2", + "@secretlint/source-creator": "^10.2.2", + "@secretlint/types": "^10.2.2", + "debug": "^4.4.1", + "p-map": "^7.0.3" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@secretlint/profiler": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/@secretlint/profiler/-/profiler-10.2.2.tgz", + "integrity": "sha512-qm9rWfkh/o8OvzMIfY8a5bCmgIniSpltbVlUVl983zDG1bUuQNd1/5lUEeWx5o/WJ99bXxS7yNI4/KIXfHexig==", + "dev": true, + "license": "MIT" + }, + "node_modules/@secretlint/resolver": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/@secretlint/resolver/-/resolver-10.2.2.tgz", + "integrity": "sha512-3md0cp12e+Ae5V+crPQYGd6aaO7ahw95s28OlULGyclyyUtf861UoRGS2prnUrKh7MZb23kdDOyGCYb9br5e4w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@secretlint/secretlint-formatter-sarif": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/@secretlint/secretlint-formatter-sarif/-/secretlint-formatter-sarif-10.2.2.tgz", + "integrity": "sha512-ojiF9TGRKJJw308DnYBucHxkpNovDNu1XvPh7IfUp0A12gzTtxuWDqdpuVezL7/IP8Ua7mp5/VkDMN9OLp1doQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "node-sarif-builder": "^3.2.0" + } + }, + "node_modules/@secretlint/secretlint-rule-no-dotenv": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/@secretlint/secretlint-rule-no-dotenv/-/secretlint-rule-no-dotenv-10.2.2.tgz", + "integrity": "sha512-KJRbIShA9DVc5Va3yArtJ6QDzGjg3PRa1uYp9As4RsyKtKSSZjI64jVca57FZ8gbuk4em0/0Jq+uy6485wxIdg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@secretlint/types": "^10.2.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@secretlint/secretlint-rule-preset-recommend": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/@secretlint/secretlint-rule-preset-recommend/-/secretlint-rule-preset-recommend-10.2.2.tgz", + "integrity": "sha512-K3jPqjva8bQndDKJqctnGfwuAxU2n9XNCPtbXVI5JvC7FnQiNg/yWlQPbMUlBXtBoBGFYp08A94m6fvtc9v+zA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@secretlint/source-creator": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/@secretlint/source-creator/-/source-creator-10.2.2.tgz", + "integrity": "sha512-h6I87xJfwfUTgQ7irWq7UTdq/Bm1RuQ/fYhA3dtTIAop5BwSFmZyrchph4WcoEvbN460BWKmk4RYSvPElIIvxw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@secretlint/types": "^10.2.2", + "istextorbinary": "^9.5.0" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@secretlint/types": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/@secretlint/types/-/types-10.2.2.tgz", + "integrity": "sha512-Nqc90v4lWCXyakD6xNyNACBJNJ0tNCwj2WNk/7ivyacYHxiITVgmLUFXTBOeCdy79iz6HtN9Y31uw/jbLrdOAg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@sinclair/typebox": { + "version": "0.27.10", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.10.tgz", + "integrity": "sha512-MTBk/3jGLNB2tVxv6uLlFh1iu64iYOQ2PbdOSK3NW8JZsmlaOh2q6sdtKowBhfw8QFLmYNzTW4/oK4uATIi6ZA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@sindresorhus/merge-streams": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/merge-streams/-/merge-streams-2.3.0.tgz", + "integrity": "sha512-LtoMMhxAlorcGhmFYI+LhPgbPZCkgP6ra1YL604EeF6U98pLlQ3iWIGMdWSC+vWmPBWBNgmDBAhnAobLROJmwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@sinonjs/commons": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz", + "integrity": "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "type-detect": "4.0.8" + } + }, + "node_modules/@sinonjs/fake-timers": { + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-10.3.0.tgz", + "integrity": "sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@sinonjs/commons": "^3.0.0" + } + }, + "node_modules/@testing-library/dom": { + "version": "10.4.1", + "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.4.1.tgz", + "integrity": "sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/code-frame": "^7.10.4", + "@babel/runtime": "^7.12.5", + "@types/aria-query": "^5.0.1", + "aria-query": "5.3.0", + "dom-accessibility-api": "^0.5.9", + "lz-string": "^1.5.0", + "picocolors": "1.1.1", + "pretty-format": "^27.0.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@testing-library/jest-dom": { + "version": "6.9.1", + "resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-6.9.1.tgz", + "integrity": "sha512-zIcONa+hVtVSSep9UT3jZ5rizo2BsxgyDYU7WFD5eICBE7no3881HGeb/QkGfsJs6JTkY1aQhT7rIPC7e+0nnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@adobe/css-tools": "^4.4.0", + "aria-query": "^5.0.0", + "css.escape": "^1.5.1", + "dom-accessibility-api": "^0.6.3", + "picocolors": "^1.1.1", + "redent": "^3.0.0" + }, + "engines": { + "node": ">=14", + "npm": ">=6", + "yarn": ">=1" + } + }, + "node_modules/@testing-library/jest-dom/node_modules/dom-accessibility-api": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.6.3.tgz", + "integrity": "sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@testing-library/react": { + "version": "16.3.2", + "resolved": "https://registry.npmjs.org/@testing-library/react/-/react-16.3.2.tgz", + "integrity": "sha512-XU5/SytQM+ykqMnAnvB2umaJNIOsLF3PVv//1Ew4CTcpz0/BRyy/af40qqrt7SjKpDdT1saBMc42CUok5gaw+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.12.5" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@testing-library/dom": "^10.0.0", + "@types/react": "^18.0.0 || ^19.0.0", + "@types/react-dom": "^18.0.0 || ^19.0.0", + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@testing-library/user-event": { + "version": "14.6.1", + "resolved": "https://registry.npmjs.org/@testing-library/user-event/-/user-event-14.6.1.tgz", + "integrity": "sha512-vq7fv0rnt+QTXgPxr5Hjc210p6YKq2kmdziLgnsZGgLJ9e6VAShx1pACLuRjd/AS/sr7phAR58OIIpf0LlmQNw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12", + "npm": ">=6" + }, + "peerDependencies": { + "@testing-library/dom": ">=7.21.4" + } + }, + "node_modules/@textlint/ast-node-types": { + "version": "15.5.2", + "resolved": "https://registry.npmjs.org/@textlint/ast-node-types/-/ast-node-types-15.5.2.tgz", + "integrity": "sha512-fCaOxoup5LIyBEo7R1oYWE7V4bSX0KQeHh66twon9e9usaLE3ijgF8QjYsR6joCssdeCHVd0wHm7ppsEyTr6vg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@textlint/linter-formatter": { + "version": "15.5.2", + "resolved": "https://registry.npmjs.org/@textlint/linter-formatter/-/linter-formatter-15.5.2.tgz", + "integrity": "sha512-jAw7jWM8+wU9cG6Uu31jGyD1B+PAVePCvnPKC/oov+2iBPKk3ao30zc/Itmi7FvXo4oPaL9PmzPPQhyniPVgVg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@azu/format-text": "^1.0.2", + "@azu/style-format": "^1.0.1", + "@textlint/module-interop": "15.5.2", + "@textlint/resolver": "15.5.2", + "@textlint/types": "15.5.2", + "chalk": "^4.1.2", + "debug": "^4.4.3", + "js-yaml": "^4.1.1", + "lodash": "^4.17.23", + "pluralize": "^2.0.0", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1", + "table": "^6.9.0", + "text-table": "^0.2.0" + } + }, + "node_modules/@textlint/linter-formatter/node_modules/pluralize": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-2.0.0.tgz", + "integrity": "sha512-TqNZzQCD4S42De9IfnnBvILN7HAW7riLqsCyp8lgjXeysyPlX5HhqKAcJHHHb9XskE4/a+7VGC9zzx8Ls0jOAw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@textlint/linter-formatter/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@textlint/module-interop": { + "version": "15.5.2", + "resolved": "https://registry.npmjs.org/@textlint/module-interop/-/module-interop-15.5.2.tgz", + "integrity": "sha512-mg6rMQ3+YjwiXCYoQXbyVfDucpTa1q5mhspd/9qHBxUq4uY6W8GU42rmT3GW0V1yOfQ9z/iRrgPtkp71s8JzXg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@textlint/resolver": { + "version": "15.5.2", + "resolved": "https://registry.npmjs.org/@textlint/resolver/-/resolver-15.5.2.tgz", + "integrity": "sha512-YEITdjRiJaQrGLUWxWXl4TEg+d2C7+TNNjbGPHPH7V7CCnXm+S9GTjGAL7Q2WSGJyFEKt88Jvx6XdJffRv4HEA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@textlint/types": { + "version": "15.5.2", + "resolved": "https://registry.npmjs.org/@textlint/types/-/types-15.5.2.tgz", + "integrity": "sha512-sJOrlVLLXp4/EZtiWKWq9y2fWyZlI8GP+24rnU5avtPWBIMm/1w97yzKrAqYF8czx2MqR391z5akhnfhj2f/AQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@textlint/ast-node-types": "15.5.2" + } + }, + "node_modules/@tootallnate/once": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz", + "integrity": "sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10" + } + }, + "node_modules/@types/aria-query": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz", + "integrity": "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "dev": true, + "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==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz", + "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.2" + } + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/graceful-fs": { + "version": "4.1.9", + "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.9.tgz", + "integrity": "sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/istanbul-lib-coverage": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", + "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/istanbul-lib-report": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz", + "integrity": "sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/istanbul-lib-coverage": "*" + } + }, + "node_modules/@types/istanbul-reports": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz", + "integrity": "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/istanbul-lib-report": "*" + } + }, + "node_modules/@types/jest": { + "version": "29.5.14", + "dev": true, + "license": "MIT", + "dependencies": { + "expect": "^29.0.0", + "pretty-format": "^29.0.0" + } + }, + "node_modules/@types/jest/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/@types/jest/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/@types/jest/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/@types/jsdom": { + "version": "20.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "@types/tough-cookie": "*", + "parse5": "^7.0.0" + } + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "20.19.37", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.37.tgz", + "integrity": "sha512-8kzdPJ3FsNsVIurqBs7oodNnCEVbni9yUEkaHbgptDACOPW04jimGagZ51E6+lXUwJjgnBw+hyko/lkFWCldqw==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/@types/normalize-package-data": { + "version": "2.4.4", + "resolved": "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.4.tgz", + "integrity": "sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/prop-types": { + "version": "15.7.15", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz", + "integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/@types/react": { + "version": "18.3.28", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.28.tgz", + "integrity": "sha512-z9VXpC7MWrhfWipitjNdgCauoMLRdIILQsAEV+ZesIzBq/oUlxk0m3ApZuMFCXdnS4U7KrI+l3WRUEGQ8K1QKw==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@types/prop-types": "*", + "csstype": "^3.2.2" + } + }, + "node_modules/@types/react-dom": { + "version": "18.3.7", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.7.tgz", + "integrity": "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@types/react": "^18.0.0" + } + }, + "node_modules/@types/sarif": { + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/@types/sarif/-/sarif-2.1.7.tgz", + "integrity": "sha512-kRz0VEkJqWLf1LLVN4pT1cg1Z9wAuvI6L97V3m2f5B76Tg8d413ddvLBPTEHAZJlnn4XSvu0FkZtViCQGVyrXQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/stack-utils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz", + "integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/tough-cookie": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.5.tgz", + "integrity": "sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/uuid": { + "version": "9.0.8", + "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-9.0.8.tgz", + "integrity": "sha512-jg+97EGIcY9AGHJJRaaPVgetKDsrTgbRjQ5Msgjh/DQKEFl0DtyRr/VCOyD1T2R1MNeWPK/u7JoGhlDZnKBAfA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/vscode": { + "version": "1.110.0", + "resolved": "https://registry.npmjs.org/@types/vscode/-/vscode-1.110.0.tgz", + "integrity": "sha512-AGuxUEpU4F4mfuQjxPPaQVyuOMhs+VT/xRok1jiHVBubHK7lBRvCuOMZG0LKUwxncrPorJ5qq/uil3IdZBd5lA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/yargs": { + "version": "17.0.35", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.35.tgz", + "integrity": "sha512-qUHkeCyQFxMXg79wQfTtfndEC+N9ZZg76HJftDJp+qH2tV7Gj4OJi7l+PiWwJ+pWtW8GwSmqsDj/oymhrTWXjg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/@types/yargs-parser": { + "version": "21.0.3", + "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.3.tgz", + "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "8.57.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.57.0.tgz", + "integrity": "sha512-qeu4rTHR3/IaFORbD16gmjq9+rEs9fGKdX0kF6BKSfi+gCuG3RCKLlSBYzn/bGsY9Tj7KE/DAQStbp8AHJGHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/regexpp": "^4.12.2", + "@typescript-eslint/scope-manager": "8.57.0", + "@typescript-eslint/type-utils": "8.57.0", + "@typescript-eslint/utils": "8.57.0", + "@typescript-eslint/visitor-keys": "8.57.0", + "ignore": "^7.0.5", + "natural-compare": "^1.4.0", + "ts-api-utils": "^2.4.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^8.57.0", + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "8.57.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.57.0.tgz", + "integrity": "sha512-XZzOmihLIr8AD1b9hL9ccNMzEMWt/dE2u7NyTY9jJG6YNiNthaD5XtUHVF2uCXZ15ng+z2hT3MVuxnUYhq6k1g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/scope-manager": "8.57.0", + "@typescript-eslint/types": "8.57.0", + "@typescript-eslint/typescript-estree": "8.57.0", + "@typescript-eslint/visitor-keys": "8.57.0", + "debug": "^4.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/project-service": { + "version": "8.57.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.57.0.tgz", + "integrity": "sha512-pR+dK0BlxCLxtWfaKQWtYr7MhKmzqZxuii+ZjuFlZlIGRZm22HnXFqa2eY+90MUz8/i80YJmzFGDUsi8dMOV5w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/tsconfig-utils": "^8.57.0", + "@typescript-eslint/types": "^8.57.0", + "debug": "^4.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "8.57.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.57.0.tgz", + "integrity": "sha512-nvExQqAHF01lUM66MskSaZulpPL5pgy5hI5RfrxviLgzZVffB5yYzw27uK/ft8QnKXI2X0LBrHJFr1TaZtAibw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.57.0", + "@typescript-eslint/visitor-keys": "8.57.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/tsconfig-utils": { + "version": "8.57.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.57.0.tgz", + "integrity": "sha512-LtXRihc5ytjJIQEH+xqjB0+YgsV4/tW35XKX3GTZHpWtcC8SPkT/d4tqdf1cKtesryHm2bgp6l555NYcT2NLvA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "8.57.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.57.0.tgz", + "integrity": "sha512-yjgh7gmDcJ1+TcEg8x3uWQmn8ifvSupnPfjP21twPKrDP/pTHlEQgmKcitzF/rzPSmv7QjJ90vRpN4U+zoUjwQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.57.0", + "@typescript-eslint/typescript-estree": "8.57.0", + "@typescript-eslint/utils": "8.57.0", + "debug": "^4.4.3", + "ts-api-utils": "^2.4.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/types": { + "version": "8.57.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.57.0.tgz", + "integrity": "sha512-dTLI8PEXhjUC7B9Kre+u0XznO696BhXcTlOn0/6kf1fHaQW8+VjJAVHJ3eTI14ZapTxdkOmc80HblPQLaEeJdg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "8.57.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.57.0.tgz", + "integrity": "sha512-m7faHcyVg0BT3VdYTlX8GdJEM7COexXxS6KqGopxdtkQRvBanK377QDHr4W/vIPAR+ah9+B/RclSW5ldVniO1Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/project-service": "8.57.0", + "@typescript-eslint/tsconfig-utils": "8.57.0", + "@typescript-eslint/types": "8.57.0", + "@typescript-eslint/visitor-keys": "8.57.0", + "debug": "^4.4.3", + "minimatch": "^10.2.2", + "semver": "^7.7.3", + "tinyglobby": "^0.2.15", + "ts-api-utils": "^2.4.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "8.57.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.57.0.tgz", + "integrity": "sha512-5iIHvpD3CZe06riAsbNxxreP+MuYgVUsV0n4bwLH//VJmgtt54sQeY2GszntJ4BjYCpMzrfVh2SBnUQTtys2lQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.9.1", + "@typescript-eslint/scope-manager": "8.57.0", + "@typescript-eslint/types": "8.57.0", + "@typescript-eslint/typescript-estree": "8.57.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "8.57.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.57.0.tgz", + "integrity": "sha512-zm6xx8UT/Xy2oSr2ZXD0pZo7Jx2XsCoID2IUh9YSTFRu7z+WdwYTRk6LhUftm1crwqbuoF6I8zAFeCMw0YjwDg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.57.0", + "eslint-visitor-keys": "^5.0.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", + "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@typespec/ts-http-runtime": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/@typespec/ts-http-runtime/-/ts-http-runtime-0.3.4.tgz", + "integrity": "sha512-CI0NhTrz4EBaa0U+HaaUZrJhPoso8sG7ZFya8uQoBA57fjzrjRSv87ekCjLZOFExN+gXE/z0xuN2QfH4H2HrLQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "http-proxy-agent": "^7.0.0", + "https-proxy-agent": "^7.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@typespec/ts-http-runtime/node_modules/agent-base": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", + "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, + "node_modules/@typespec/ts-http-runtime/node_modules/http-proxy-agent": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", + "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.0", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/@typespec/ts-http-runtime/node_modules/https-proxy-agent": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/@vitejs/plugin-react": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.7.0.tgz", + "integrity": "sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==", + "dev": true, + "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" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "peerDependencies": { + "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" + } + }, + "node_modules/@vscode/codicons": { + "version": "0.0.36", + "resolved": "https://registry.npmjs.org/@vscode/codicons/-/codicons-0.0.36.tgz", + "integrity": "sha512-wsNOvNMMJ2BY8rC2N2MNBG7yOowV3ov8KlvUE/AiVUlHKTfWsw3OgAOQduX7h0Un6GssKD3aoTVH+TF3DSQwKQ==", + "license": "CC-BY-4.0" + }, + "node_modules/@vscode/python-extension": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/@vscode/python-extension/-/python-extension-1.0.6.tgz", + "integrity": "sha512-q7KYf+mymM67G0yS6xfhczFwu2teBi5oXHVdNgtCsYhErdSOkwMPtE291SqQahrjTcgKxn7O56i1qb1EQR6o4w==", + "license": "MIT", + "engines": { + "node": ">=22.17.0", + "vscode": "^1.93.0" + } + }, + "node_modules/@vscode/vsce": { + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/@vscode/vsce/-/vsce-3.7.1.tgz", + "integrity": "sha512-OTm2XdMt2YkpSn2Nx7z2EJtSuhRHsTPYsSK59hr3v8jRArK+2UEoju4Jumn1CmpgoBLGI6ReHLJ/czYltNUW3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@azure/identity": "^4.1.0", + "@secretlint/node": "^10.1.2", + "@secretlint/secretlint-formatter-sarif": "^10.1.2", + "@secretlint/secretlint-rule-no-dotenv": "^10.1.2", + "@secretlint/secretlint-rule-preset-recommend": "^10.1.2", + "@vscode/vsce-sign": "^2.0.0", + "azure-devops-node-api": "^12.5.0", + "chalk": "^4.1.2", + "cheerio": "^1.0.0-rc.9", + "cockatiel": "^3.1.2", + "commander": "^12.1.0", + "form-data": "^4.0.0", + "glob": "^11.0.0", + "hosted-git-info": "^4.0.2", + "jsonc-parser": "^3.2.0", + "leven": "^3.1.0", + "markdown-it": "^14.1.0", + "mime": "^1.3.4", + "minimatch": "^3.0.3", + "parse-semver": "^1.1.1", + "read": "^1.0.7", + "secretlint": "^10.1.2", + "semver": "^7.5.2", + "tmp": "^0.2.3", + "typed-rest-client": "^1.8.4", + "url-join": "^4.0.1", + "xml2js": "^0.5.0", + "yauzl": "^2.3.1", + "yazl": "^2.2.2" + }, + "bin": { + "vsce": "vsce" + }, + "engines": { + "node": ">= 20" + }, + "optionalDependencies": { + "keytar": "^7.7.0" + } + }, + "node_modules/@vscode/vsce-sign": { + "version": "2.0.9", + "resolved": "https://registry.npmjs.org/@vscode/vsce-sign/-/vsce-sign-2.0.9.tgz", + "integrity": "sha512-8IvaRvtFyzUnGGl3f5+1Cnor3LqaUWvhaUjAYO8Y39OUYlOf3cRd+dowuQYLpZcP3uwSG+mURwjEBOSq4SOJ0g==", + "dev": true, + "hasInstallScript": true, + "license": "SEE LICENSE IN LICENSE.txt", + "optionalDependencies": { + "@vscode/vsce-sign-alpine-arm64": "2.0.6", + "@vscode/vsce-sign-alpine-x64": "2.0.6", + "@vscode/vsce-sign-darwin-arm64": "2.0.6", + "@vscode/vsce-sign-darwin-x64": "2.0.6", + "@vscode/vsce-sign-linux-arm": "2.0.6", + "@vscode/vsce-sign-linux-arm64": "2.0.6", + "@vscode/vsce-sign-linux-x64": "2.0.6", + "@vscode/vsce-sign-win32-arm64": "2.0.6", + "@vscode/vsce-sign-win32-x64": "2.0.6" + } + }, + "node_modules/@vscode/vsce-sign-alpine-arm64": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@vscode/vsce-sign-alpine-arm64/-/vsce-sign-alpine-arm64-2.0.6.tgz", + "integrity": "sha512-wKkJBsvKF+f0GfsUuGT0tSW0kZL87QggEiqNqK6/8hvqsXvpx8OsTEc3mnE1kejkh5r+qUyQ7PtF8jZYN0mo8Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "SEE LICENSE IN LICENSE.txt", + "optional": true, + "os": [ + "alpine" + ] + }, + "node_modules/@vscode/vsce-sign-alpine-x64": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@vscode/vsce-sign-alpine-x64/-/vsce-sign-alpine-x64-2.0.6.tgz", + "integrity": "sha512-YoAGlmdK39vKi9jA18i4ufBbd95OqGJxRvF3n6ZbCyziwy3O+JgOpIUPxv5tjeO6gQfx29qBivQ8ZZTUF2Ba0w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "SEE LICENSE IN LICENSE.txt", + "optional": true, + "os": [ + "alpine" + ] + }, + "node_modules/@vscode/vsce-sign-darwin-arm64": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@vscode/vsce-sign-darwin-arm64/-/vsce-sign-darwin-arm64-2.0.6.tgz", + "integrity": "sha512-5HMHaJRIQuozm/XQIiJiA0W9uhdblwwl2ZNDSSAeXGO9YhB9MH5C4KIHOmvyjUnKy4UCuiP43VKpIxW1VWP4tQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "SEE LICENSE IN LICENSE.txt", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@vscode/vsce-sign-darwin-x64": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@vscode/vsce-sign-darwin-x64/-/vsce-sign-darwin-x64-2.0.6.tgz", + "integrity": "sha512-25GsUbTAiNfHSuRItoQafXOIpxlYj+IXb4/qarrXu7kmbH94jlm5sdWSCKrrREs8+GsXF1b+l3OB7VJy5jsykw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "SEE LICENSE IN LICENSE.txt", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@vscode/vsce-sign-linux-arm": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@vscode/vsce-sign-linux-arm/-/vsce-sign-linux-arm-2.0.6.tgz", + "integrity": "sha512-UndEc2Xlq4HsuMPnwu7420uqceXjs4yb5W8E2/UkaHBB9OWCwMd3/bRe/1eLe3D8kPpxzcaeTyXiK3RdzS/1CA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "SEE LICENSE IN LICENSE.txt", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@vscode/vsce-sign-linux-arm64": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@vscode/vsce-sign-linux-arm64/-/vsce-sign-linux-arm64-2.0.6.tgz", + "integrity": "sha512-cfb1qK7lygtMa4NUl2582nP7aliLYuDEVpAbXJMkDq1qE+olIw/es+C8j1LJwvcRq1I2yWGtSn3EkDp9Dq5FdA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "SEE LICENSE IN LICENSE.txt", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@vscode/vsce-sign-linux-x64": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@vscode/vsce-sign-linux-x64/-/vsce-sign-linux-x64-2.0.6.tgz", + "integrity": "sha512-/olerl1A4sOqdP+hjvJ1sbQjKN07Y3DVnxO4gnbn/ahtQvFrdhUi0G1VsZXDNjfqmXw57DmPi5ASnj/8PGZhAA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "SEE LICENSE IN LICENSE.txt", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@vscode/vsce-sign-win32-arm64": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@vscode/vsce-sign-win32-arm64/-/vsce-sign-win32-arm64-2.0.6.tgz", + "integrity": "sha512-ivM/MiGIY0PJNZBoGtlRBM/xDpwbdlCWomUWuLmIxbi1Cxe/1nooYrEQoaHD8ojVRgzdQEUzMsRbyF5cJJgYOg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "SEE LICENSE IN LICENSE.txt", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@vscode/vsce-sign-win32-x64": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@vscode/vsce-sign-win32-x64/-/vsce-sign-win32-x64-2.0.6.tgz", + "integrity": "sha512-mgth9Kvze+u8CruYMmhHw6Zgy3GRX2S+Ed5oSokDEK5vPEwGGKnmuXua9tmFhomeAnhgJnL4DCna3TiNuGrBTQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "SEE LICENSE IN LICENSE.txt", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@vscode/vsce/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@vscode/vsce/node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@vscode/vsce/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@vscode/webview-ui-toolkit": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@vscode/webview-ui-toolkit/-/webview-ui-toolkit-1.4.0.tgz", + "integrity": "sha512-modXVHQkZLsxgmd5yoP3ptRC/G8NBDD+ob+ngPiWNQdlrH6H1xR/qgOBD85bfU3BhOB5sZzFWBwwhp9/SfoHww==", + "deprecated": "This package has been deprecated, https://github.com/microsoft/vscode-webview-ui-toolkit/issues/561", + "license": "MIT", + "dependencies": { + "@microsoft/fast-element": "^1.12.0", + "@microsoft/fast-foundation": "^2.49.4", + "@microsoft/fast-react-wrapper": "^0.3.22", + "tslib": "^2.6.2" + }, + "peerDependencies": { + "react": ">=16.9.0" + } + }, + "node_modules/abab": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.6.tgz", + "integrity": "sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA==", + "deprecated": "Use your platform's native atob() and btoa() methods instead", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/acorn": { + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", + "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-globals": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-7.0.1.tgz", + "integrity": "sha512-umOSDSDrfHbTNPuNpC2NSnnA3LUrqpevPb4T9jRx4MagXNS0rs+gwiTcAvqCRmsD6utzsrzNt+ebm00SNWiC3Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "acorn": "^8.1.0", + "acorn-walk": "^8.0.2" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/acorn-walk": { + "version": "8.3.5", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.5.tgz", + "integrity": "sha512-HEHNfbars9v4pgpW6SO1KSPkfoS0xVOM/9UzkJltjlsHZmJasxg8aXkuZa7SMf8vKGIBhpUsPluQSqhJFCqebw==", + "dev": true, + "license": "MIT", + "dependencies": { + "acorn": "^8.11.0" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/agent-base": { + "version": "6.0.2", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/ajv": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", + "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-escapes": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", + "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "type-fest": "^0.21.3" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "license": "ISC", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0" + }, + "node_modules/aria-query": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.0.tgz", + "integrity": "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "dequal": "^2.0.3" + } + }, + "node_modules/astral-regex": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", + "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/azure-devops-node-api": { + "version": "12.5.0", + "resolved": "https://registry.npmjs.org/azure-devops-node-api/-/azure-devops-node-api-12.5.0.tgz", + "integrity": "sha512-R5eFskGvOm3U/GzeAuxRkUsAl0hrAwGgWn6zAd2KrZmrEhWZVqLew4OOupbQlXUuojUzpGtq62SmdhJ06N88og==", + "dev": true, + "license": "MIT", + "dependencies": { + "tunnel": "0.0.6", + "typed-rest-client": "^1.8.4" + } + }, + "node_modules/babel-jest": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.7.0.tgz", + "integrity": "sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/transform": "^29.7.0", + "@types/babel__core": "^7.1.14", + "babel-plugin-istanbul": "^6.1.1", + "babel-preset-jest": "^29.6.3", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.8.0" + } + }, + "node_modules/babel-plugin-istanbul": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz", + "integrity": "sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@babel/helper-plugin-utils": "^7.0.0", + "@istanbuljs/load-nyc-config": "^1.0.0", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-instrument": "^5.0.4", + "test-exclude": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/babel-plugin-istanbul/node_modules/istanbul-lib-instrument": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz", + "integrity": "sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@babel/core": "^7.12.3", + "@babel/parser": "^7.14.7", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/babel-plugin-istanbul/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-plugin-jest-hoist": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.6.3.tgz", + "integrity": "sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.3.3", + "@babel/types": "^7.3.3", + "@types/babel__core": "^7.1.14", + "@types/babel__traverse": "^7.0.6" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/babel-preset-current-node-syntax": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.2.0.tgz", + "integrity": "sha512-E/VlAEzRrsLEb2+dv8yp3bo4scof3l9nR4lrld+Iy5NyVqgVYUJnDAmunkhPMisRI32Qc4iRiz425d8vM++2fg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/plugin-syntax-async-generators": "^7.8.4", + "@babel/plugin-syntax-bigint": "^7.8.3", + "@babel/plugin-syntax-class-properties": "^7.12.13", + "@babel/plugin-syntax-class-static-block": "^7.14.5", + "@babel/plugin-syntax-import-attributes": "^7.24.7", + "@babel/plugin-syntax-import-meta": "^7.10.4", + "@babel/plugin-syntax-json-strings": "^7.8.3", + "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", + "@babel/plugin-syntax-numeric-separator": "^7.10.4", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", + "@babel/plugin-syntax-optional-chaining": "^7.8.3", + "@babel/plugin-syntax-private-property-in-object": "^7.14.5", + "@babel/plugin-syntax-top-level-await": "^7.14.5" + }, + "peerDependencies": { + "@babel/core": "^7.0.0 || ^8.0.0-0" + } + }, + "node_modules/babel-preset-jest": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-29.6.3.tgz", + "integrity": "sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA==", + "dev": true, + "license": "MIT", + "dependencies": { + "babel-plugin-jest-hoist": "^29.6.3", + "babel-preset-current-node-syntax": "^1.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "optional": true + }, + "node_modules/baseline-browser-mapping": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.0.tgz", + "integrity": "sha512-lIyg0szRfYbiy67j9KN8IyeD7q7hcmqnJ1ddWmNt19ItGpNN64mnllmxUNFIOdOm6by97jlL6wfpTTJrmnjWAA==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/binaryextensions": { + "version": "6.11.0", + "resolved": "https://registry.npmjs.org/binaryextensions/-/binaryextensions-6.11.0.tgz", + "integrity": "sha512-sXnYK/Ij80TO3lcqZVV2YgfKN5QjUWIRk/XSm2J/4bd/lPko3lvk0O4ZppH6m+6hB2/GTu+ptNwVFe1xh+QLQw==", + "dev": true, + "license": "Artistic-2.0", + "dependencies": { + "editions": "^6.21.0" + }, + "engines": { + "node": ">=4" + }, + "funding": { + "url": "https://bevry.me/fund" + } + }, + "node_modules/bl": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, + "node_modules/boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", + "dev": true, + "license": "ISC" + }, + "node_modules/boundary": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/boundary/-/boundary-2.0.0.tgz", + "integrity": "sha512-rJKn5ooC9u8q13IMCrW0RSp31pxBCHE3y9V/tp3TdWSLf8Em3p6Di4NBpfzbJge9YjjFEsD0RtFEjtvHL5VyEA==", + "dev": true, + "license": "BSD-2-Clause" + }, + "node_modules/brace-expansion": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.4.tgz", + "integrity": "sha512-h+DEnpVvxmfVefa4jFbCf5HdH5YMDXRsmKflpf1pILZWRFlTbJpxeU55nJl4Smt5HQaGzg1o6RHFPJaOqnmBDg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "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/bs-logger": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/bs-logger/-/bs-logger-0.2.6.tgz", + "integrity": "sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-json-stable-stringify": "2.x" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/bser": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", + "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "node-int64": "^0.4.0" + } + }, + "node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "optional": true, + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "node_modules/buffer-crc32": { + "version": "0.2.13", + "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", + "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/bundle-name": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bundle-name/-/bundle-name-4.1.0.tgz", + "integrity": "sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "run-applescript": "^7.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001777", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001777.tgz", + "integrity": "sha512-tmN+fJxroPndC74efCdp12j+0rk0RHwV5Jwa1zWaFVyw2ZxAuPeG8ZgWC3Wz7uSjT3qMRQ5XHZ4COgQmsCMJAQ==", + "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/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/char-regex": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", + "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/cheerio": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.2.0.tgz", + "integrity": "sha512-WDrybc/gKFpTYQutKIK6UvfcuxijIZfMfXaYm8NMsPQxSYvf+13fXUJ4rztGGbJcBQ/GF55gvrZ0Bc0bj/mqvg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cheerio-select": "^2.1.0", + "dom-serializer": "^2.0.0", + "domhandler": "^5.0.3", + "domutils": "^3.2.2", + "encoding-sniffer": "^0.2.1", + "htmlparser2": "^10.1.0", + "parse5": "^7.3.0", + "parse5-htmlparser2-tree-adapter": "^7.1.0", + "parse5-parser-stream": "^7.1.2", + "undici": "^7.19.0", + "whatwg-mimetype": "^4.0.0" + }, + "engines": { + "node": ">=20.18.1" + }, + "funding": { + "url": "https://github.com/cheeriojs/cheerio?sponsor=1" + } + }, + "node_modules/cheerio-select": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cheerio-select/-/cheerio-select-2.1.0.tgz", + "integrity": "sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "boolbase": "^1.0.0", + "css-select": "^5.1.0", + "css-what": "^6.1.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3", + "domutils": "^3.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/cheerio/node_modules/whatwg-mimetype": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz", + "integrity": "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/chownr": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", + "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", + "dev": true, + "license": "ISC", + "optional": true + }, + "node_modules/ci-info": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", + "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/cjs-module-lexer": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.4.3.tgz", + "integrity": "sha512-9z8TZaGM1pfswYeXrUpzPrkx8UnWYdhJclsiYMm6x/w5+nN+8Tf/LnAgfLGQCm59qAOxU8WwHEq2vNwF6i4j+Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/cliui/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/co": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", + "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==", + "dev": true, + "license": "MIT", + "engines": { + "iojs": ">= 1.0.0", + "node": ">= 0.12.0" + } + }, + "node_modules/cockatiel": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/cockatiel/-/cockatiel-3.2.1.tgz", + "integrity": "sha512-gfrHV6ZPkquExvMh9IOkKsBzNDk6sDuZ6DdBGUBkvFnTCqCxzpuq48RySgP0AnaqQkw2zynOFj9yly6T1Q2G5Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=16" + } + }, + "node_modules/collect-v8-coverage": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.3.tgz", + "integrity": "sha512-1L5aqIkwPfiodaMgQunkF1zRhNqifHBmtbbbxcr6yVxxBnliw4TDOW6NxpO8DJLgJ16OT+Y4ztZqP6p/FtXnAw==", + "dev": true, + "license": "MIT" + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dev": true, + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/commander": { + "version": "12.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz", + "integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "license": "MIT" + }, + "node_modules/concurrently": { + "version": "8.2.2", + "resolved": "https://registry.npmjs.org/concurrently/-/concurrently-8.2.2.tgz", + "integrity": "sha512-1dP4gpXFhei8IOtlXRE/T/4H88ElHgTiUzh71YUmtjTEHMSRS2Z/fgOxHSxxusGHogsRfxNq1vyAwxSC+EVyDg==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.1.2", + "date-fns": "^2.30.0", + "lodash": "^4.17.21", + "rxjs": "^7.8.1", + "shell-quote": "^1.8.1", + "spawn-command": "0.0.2", + "supports-color": "^8.1.1", + "tree-kill": "^1.2.2", + "yargs": "^17.7.2" + }, + "bin": { + "conc": "dist/bin/concurrently.js", + "concurrently": "dist/bin/concurrently.js" + }, + "engines": { + "node": "^14.13.0 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/open-cli-tools/concurrently?sponsor=1" + } + }, + "node_modules/concurrently/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/create-jest": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/create-jest/-/create-jest-29.7.0.tgz", + "integrity": "sha512-Adz2bdH0Vq3F53KEMJOoftQFutWCukm6J24wbPWRO4k1kMY7gS7ds/uoJkNuV8wDCtWWnuwGcJwpWcih+zEW1Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "chalk": "^4.0.0", + "exit": "^0.1.2", + "graceful-fs": "^4.2.9", + "jest-config": "^29.7.0", + "jest-util": "^29.7.0", + "prompts": "^2.0.1" + }, + "bin": { + "create-jest": "bin/create-jest.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/css-select": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.2.2.tgz", + "integrity": "sha512-TizTzUddG/xYLA3NXodFM0fSbNizXjOKhqiQQwvhlspadZokn1KDy0NZFS0wuEubIYAV5/c1/lAr0TaaFXEXzw==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "boolbase": "^1.0.0", + "css-what": "^6.1.0", + "domhandler": "^5.0.2", + "domutils": "^3.0.1", + "nth-check": "^2.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/css-what": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.2.2.tgz", + "integrity": "sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">= 6" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/css.escape": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/css.escape/-/css.escape-1.5.1.tgz", + "integrity": "sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==", + "dev": true, + "license": "MIT" + }, + "node_modules/cssom": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.5.0.tgz", + "integrity": "sha512-iKuQcq+NdHqlAcwUY0o/HL69XQrUaQdMjmStJ8JFmUaiiQErlhrmuigkg/CU4E2J0IyUKUrMAgl36TvN67MqTw==", + "dev": true, + "license": "MIT" + }, + "node_modules/cssstyle": { + "version": "2.3.0", + "dev": true, + "license": "MIT", + "dependencies": { + "cssom": "~0.3.6" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cssstyle/node_modules/cssom": { + "version": "0.3.8", + "dev": true, + "license": "MIT" + }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/data-urls": { + "version": "3.0.2", + "dev": true, + "license": "MIT", + "dependencies": { + "abab": "^2.0.6", + "whatwg-mimetype": "^3.0.0", + "whatwg-url": "^11.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/date-fns": { + "version": "2.30.0", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.30.0.tgz", + "integrity": "sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.21.0" + }, + "engines": { + "node": ">=0.11" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/date-fns" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decimal.js": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.6.0.tgz", + "integrity": "sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==", + "dev": true, + "license": "MIT" + }, + "node_modules/decompress-response": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", + "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "mimic-response": "^3.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/dedent": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.7.2.tgz", + "integrity": "sha512-WzMx3mW98SN+zn3hgemf4OzdmyNhhhKz5Ay0pUfQiMQ3e1g+xmTJWp/pKdwKVXhdSkAEGIIzqeuWrL3mV/AXbA==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "babel-plugin-macros": "^3.1.0" + }, + "peerDependenciesMeta": { + "babel-plugin-macros": { + "optional": true + } + } + }, + "node_modules/deep-extend": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/deepmerge": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/default-browser": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/default-browser/-/default-browser-5.5.0.tgz", + "integrity": "sha512-H9LMLr5zwIbSxrmvikGuI/5KGhZ8E2zH3stkMgM5LpOWDutGM2JZaj460Udnf1a+946zc7YBgrqEWwbk7zHvGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "bundle-name": "^4.1.0", + "default-browser-id": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/default-browser-id": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/default-browser-id/-/default-browser-id-5.0.1.tgz", + "integrity": "sha512-x1VCxdX4t+8wVfd1so/9w+vQ4vx7lKd2Qp5tDRutErwmR85OgmfX7RlLRMWafRMY7hbEiXIbudNrjOAPa/hL8Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/define-lazy-prop": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-3.0.0.tgz", + "integrity": "sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "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": "Apache-2.0", + "optional": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/detect-newline": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", + "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/diff-sequences": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz", + "integrity": "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/dom-accessibility-api": { + "version": "0.5.16", + "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz", + "integrity": "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/dom-serializer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", + "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", + "dev": true, + "license": "MIT", + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.2", + "entities": "^4.2.0" + }, + "funding": { + "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" + } + }, + "node_modules/domelementtype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", + "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "license": "BSD-2-Clause" + }, + "node_modules/domexception": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/domexception/-/domexception-4.0.0.tgz", + "integrity": "sha512-A2is4PLG+eeSfoTMA95/s4pvAoSo2mKtiM5jlHkAVewmiO8ISFTFKZjH7UAM1Atli/OT/7JHOrJRJiMKUZKYBw==", + "deprecated": "Use your platform's native DOMException instead", + "dev": true, + "license": "MIT", + "dependencies": { + "webidl-conversions": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/domhandler": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", + "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "domelementtype": "^2.3.0" + }, + "engines": { + "node": ">= 4" + }, + "funding": { + "url": "https://github.com/fb55/domhandler?sponsor=1" + } + }, + "node_modules/domutils": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.2.2.tgz", + "integrity": "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "dom-serializer": "^2.0.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3" + }, + "funding": { + "url": "https://github.com/fb55/domutils?sponsor=1" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "safe-buffer": "^5.0.1" + } + }, + "node_modules/editions": { + "version": "6.22.0", + "resolved": "https://registry.npmjs.org/editions/-/editions-6.22.0.tgz", + "integrity": "sha512-UgGlf8IW75je7HZjNDpJdCv4cGJWIi6yumFdZ0R7A8/CIhQiWUjyGLCxdHpd8bmyD1gnkfUNK0oeOXqUS2cpfQ==", + "dev": true, + "license": "Artistic-2.0", + "dependencies": { + "version-range": "^4.15.0" + }, + "engines": { + "ecmascript": ">= es5", + "node": ">=4" + }, + "funding": { + "url": "https://bevry.me/fund" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.5.307", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.307.tgz", + "integrity": "sha512-5z3uFKBWjiNR44nFcYdkcXjKMbg5KXNdciu7mhTPo9tB7NbqSNP2sSnGR+fqknZSCwKkBN+oxiiajWs4dT6ORg==", + "dev": true, + "license": "ISC" + }, + "node_modules/emittery": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.13.1.tgz", + "integrity": "sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sindresorhus/emittery?sponsor=1" + } + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/encoding-sniffer": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/encoding-sniffer/-/encoding-sniffer-0.2.1.tgz", + "integrity": "sha512-5gvq20T6vfpekVtqrYQsSCFZ1wEg5+wW0/QaZMWkFr6BqD3NfKs0rLCx4rrVlSWJeZb5NBJgVLswK/w2MWU+Gw==", + "dev": true, + "license": "MIT", + "dependencies": { + "iconv-lite": "^0.6.3", + "whatwg-encoding": "^3.1.1" + }, + "funding": { + "url": "https://github.com/fb55/encoding-sniffer?sponsor=1" + } + }, + "node_modules/encoding-sniffer/node_modules/whatwg-encoding": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz", + "integrity": "sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==", + "deprecated": "Use @exodus/bytes instead for a more spec-conformant and faster implementation", + "dev": true, + "license": "MIT", + "dependencies": { + "iconv-lite": "0.6.3" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/end-of-stream": { + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", + "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "once": "^1.4.0" + } + }, + "node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/environment": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/environment/-/environment-1.1.0.tgz", + "integrity": "sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/error-ex": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.4.tgz", + "integrity": "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/esbuild": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.3.tgz", + "integrity": "sha512-8VwMnyGCONIs6cWue2IdpHxHnAjzxnw2Zr7MkVxB2vjmQ2ivqGFb4LEG3SMnv0Gb2F/G/2yA8zUaiL1gywDCCg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.27.3", + "@esbuild/android-arm": "0.27.3", + "@esbuild/android-arm64": "0.27.3", + "@esbuild/android-x64": "0.27.3", + "@esbuild/darwin-arm64": "0.27.3", + "@esbuild/darwin-x64": "0.27.3", + "@esbuild/freebsd-arm64": "0.27.3", + "@esbuild/freebsd-x64": "0.27.3", + "@esbuild/linux-arm": "0.27.3", + "@esbuild/linux-arm64": "0.27.3", + "@esbuild/linux-ia32": "0.27.3", + "@esbuild/linux-loong64": "0.27.3", + "@esbuild/linux-mips64el": "0.27.3", + "@esbuild/linux-ppc64": "0.27.3", + "@esbuild/linux-riscv64": "0.27.3", + "@esbuild/linux-s390x": "0.27.3", + "@esbuild/linux-x64": "0.27.3", + "@esbuild/netbsd-arm64": "0.27.3", + "@esbuild/netbsd-x64": "0.27.3", + "@esbuild/openbsd-arm64": "0.27.3", + "@esbuild/openbsd-x64": "0.27.3", + "@esbuild/openharmony-arm64": "0.27.3", + "@esbuild/sunos-x64": "0.27.3", + "@esbuild/win32-arm64": "0.27.3", + "@esbuild/win32-ia32": "0.27.3", + "@esbuild/win32-x64": "0.27.3" + } + }, + "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", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/escodegen": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.1.0.tgz", + "integrity": "sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esprima": "^4.0.1", + "estraverse": "^5.2.0", + "esutils": "^2.0.2" + }, + "bin": { + "escodegen": "bin/escodegen.js", + "esgenerate": "bin/esgenerate.js" + }, + "engines": { + "node": ">=6.0" + }, + "optionalDependencies": { + "source-map": "~0.6.1" + } + }, + "node_modules/eslint": { + "version": "9.39.4", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.4.tgz", + "integrity": "sha512-XoMjdBOwe/esVgEvLmNsD3IRHkm7fbKIUGvrleloJXUZgDHig2IPWNniv+GwjyJXzuNqVjlr5+4yVUZjycJwfQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.8.0", + "@eslint-community/regexpp": "^4.12.1", + "@eslint/config-array": "^0.21.2", + "@eslint/config-helpers": "^0.4.2", + "@eslint/core": "^0.17.0", + "@eslint/eslintrc": "^3.3.5", + "@eslint/js": "9.39.4", + "@eslint/plugin-kit": "^0.4.1", + "@humanfs/node": "^0.16.6", + "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.4.2", + "@types/estree": "^1.0.6", + "ajv": "^6.14.0", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.6", + "debug": "^4.3.2", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^8.4.0", + "eslint-visitor-keys": "^4.2.1", + "espree": "^10.4.0", + "esquery": "^1.5.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^8.0.0", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.5", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } + } + }, + "node_modules/eslint-config-prettier": { + "version": "10.1.8", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-10.1.8.tgz", + "integrity": "sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w==", + "dev": true, + "license": "MIT", + "bin": { + "eslint-config-prettier": "bin/cli.js" + }, + "funding": { + "url": "https://opencollective.com/eslint-config-prettier" + }, + "peerDependencies": { + "eslint": ">=7.0.0" + } + }, + "node_modules/eslint-scope": { + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", + "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/ajv": { + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz", + "integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/eslint/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/eslint/node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/eslint/node_modules/eslint-visitor-keys": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/eslint/node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, + "node_modules/eslint/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/espree": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", + "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.15.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^4.2.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/espree/node_modules/eslint-visitor-keys": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true, + "license": "BSD-2-Clause", + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/esquery": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz", + "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/execa": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/execa/node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/exenv-es6": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/exenv-es6/-/exenv-es6-1.1.1.tgz", + "integrity": "sha512-vlVu3N8d6yEMpMsEm+7sUBAI81aqYYuEvfK0jNqmdb/OPXzzH7QWDDnVjMvDSY47JdHEqx/dfC/q8WkfoTmpGQ==", + "license": "MIT" + }, + "node_modules/exit": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", + "integrity": "sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/expand-template": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz", + "integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==", + "dev": true, + "license": "(MIT OR WTFPL)", + "optional": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/expect": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/expect/-/expect-29.7.0.tgz", + "integrity": "sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/expect-utils": "^29.7.0", + "jest-get-type": "^29.6.3", + "jest-matcher-utils": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.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" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz", + "integrity": "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/fastq": { + "version": "1.20.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz", + "integrity": "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==", + "dev": true, + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/fb-watchman": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz", + "integrity": "sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "bser": "2.1.1" + } + }, + "node_modules/fd-slicer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", + "integrity": "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "pend": "~1.2.0" + } + }, + "node_modules/file-entry-cache": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "flat-cache": "^4.0.0" + }, + "engines": { + "node": ">=16.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", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.4" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/flatted": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.1.tgz", + "integrity": "sha512-IxfVbRFVlV8V/yRaGzk0UVIcsKKHMSfYw66T/u4nTwlWteQePsxe//LjudR1AMX4tZW3WFCh3Zqa/sjlqpbURQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/foreground-child": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", + "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", + "dev": true, + "license": "ISC", + "dependencies": { + "cross-spawn": "^7.0.6", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/form-data": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", + "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", + "dev": true, + "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" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fs-constants": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", + "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/fs-extra": { + "version": "11.3.4", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.4.tgz", + "integrity": "sha512-CTXd6rk/M3/ULNQj8FBqBWHYBVYybQ3VPBw0xGKFe3tuH7ytT6ACnvzpIQ3UZtB8yvUKC2cXn1a+x+5EVQLovA==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=14.14" + } + }, + "node_modules/fs-extra/node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true, + "license": "ISC" + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-package-type": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", + "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/github-from-package": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz", + "integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/glob": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-11.1.0.tgz", + "integrity": "sha512-vuNwKSaKiqm7g0THUBu2x7ckSs3XJLXE+2ssL7/MfTGPLLcrJQ/4Uq1CjPTtO5cCIiRxqvN6Twy1qOwhL0Xjcw==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "foreground-child": "^3.3.1", + "jackspeak": "^4.1.1", + "minimatch": "^10.1.1", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^2.0.0" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/globals": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", + "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globby": { + "version": "14.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-14.1.0.tgz", + "integrity": "sha512-0Ia46fDOaT7k4og1PDW4YbodWWr3scS2vAr2lTbsplOt2WkKp0vQbkI9wKis/T5LV/dqPjO3bpS/z6GTJB82LA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sindresorhus/merge-streams": "^2.1.0", + "fast-glob": "^3.3.3", + "ignore": "^7.0.3", + "path-type": "^6.0.0", + "slash": "^5.1.0", + "unicorn-magic": "^0.3.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globby/node_modules/slash": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-5.1.0.tgz", + "integrity": "sha512-ZA6oR3T/pEyuqwMgAKT0/hAv8oAXckzbkmR0UkUosQ+Mc4RxGoJkRmwHgHufaenlyAgE1Mxgpdcrf75y6XcnDg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/handlebars": { + "version": "4.7.8", + "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.8.tgz", + "integrity": "sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "minimist": "^1.2.5", + "neo-async": "^2.6.2", + "source-map": "^0.6.1", + "wordwrap": "^1.0.0" + }, + "bin": { + "handlebars": "bin/handlebars" + }, + "engines": { + "node": ">=0.4.7" + }, + "optionalDependencies": { + "uglify-js": "^3.1.4" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/hosted-git-info": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-4.1.0.tgz", + "integrity": "sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA==", + "dev": true, + "license": "ISC", + "dependencies": { + "lru-cache": "^6.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/hosted-git-info/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/hosted-git-info/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true, + "license": "ISC" + }, + "node_modules/html-encoding-sniffer": { + "version": "3.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "whatwg-encoding": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/html-escaper": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "dev": true, + "license": "MIT" + }, + "node_modules/htmlparser2": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-10.1.0.tgz", + "integrity": "sha512-VTZkM9GWRAtEpveh7MSF6SjjrpNVNNVJfFup7xTY3UpFtm67foy9HDVXneLtFVt4pMz5kZtgNcvCniNFb1hlEQ==", + "dev": true, + "funding": [ + "https://github.com/fb55/htmlparser2?sponsor=1", + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "license": "MIT", + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3", + "domutils": "^3.2.2", + "entities": "^7.0.1" + } + }, + "node_modules/htmlparser2/node_modules/entities": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-7.0.1.tgz", + "integrity": "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/http-proxy-agent": { + "version": "5.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@tootallnate/once": "2", + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/https-proxy-agent": { + "version": "5.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=10.17.0" + } + }, + "node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "BSD-3-Clause", + "optional": true + }, + "node_modules/ignore": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", + "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/import-fresh": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/import-local": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.2.0.tgz", + "integrity": "sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA==", + "dev": true, + "license": "MIT", + "dependencies": { + "pkg-dir": "^4.2.0", + "resolve-cwd": "^3.0.0" + }, + "bin": { + "import-local-fixture": "fixtures/cli.js" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/indent-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/index-to-position": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/index-to-position/-/index-to-position-1.2.0.tgz", + "integrity": "sha512-Yg7+ztRkqslMAS2iFaU+Oa4KTSidr63OsFGlOrJoW981kIYO3CGCS3wA95P1mUi/IVSJkn0D479KTJpVpvFNuw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "dev": true, + "license": "ISC", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "dev": true, + "license": "ISC", + "optional": true + }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "dev": true, + "license": "MIT" + }, + "node_modules/is-core-module": { + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", + "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", + "dev": true, + "license": "MIT", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-docker": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-3.0.0.tgz", + "integrity": "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==", + "dev": true, + "license": "MIT", + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-generator-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz", + "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-inside-container": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-inside-container/-/is-inside-container-1.0.0.tgz", + "integrity": "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-docker": "^3.0.0" + }, + "bin": { + "is-inside-container": "cli.js" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-potential-custom-element-name": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", + "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-wsl": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-3.1.1.tgz", + "integrity": "sha512-e6rvdUCiQCAuumZslxRJWR/Doq4VpPR82kqclvcS0efgt430SlGIk05vdCN58+VrzgtIcfNODjozVielycD4Sw==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-inside-container": "^1.0.0" + }, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/istanbul-lib-coverage": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", + "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-instrument": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.3.tgz", + "integrity": "sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@babel/core": "^7.23.9", + "@babel/parser": "^7.23.9", + "@istanbuljs/schema": "^0.1.3", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^7.5.4" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-report": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", + "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^4.0.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-source-maps": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz", + "integrity": "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "debug": "^4.1.1", + "istanbul-lib-coverage": "^3.0.0", + "source-map": "^0.6.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-reports": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.2.0.tgz", + "integrity": "sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/istextorbinary": { + "version": "9.5.0", + "resolved": "https://registry.npmjs.org/istextorbinary/-/istextorbinary-9.5.0.tgz", + "integrity": "sha512-5mbUj3SiZXCuRf9fT3ibzbSSEWiy63gFfksmGfdOzujPjW3k+z8WvIBxcJHBoQNlaZaiyB25deviif2+osLmLw==", + "dev": true, + "license": "Artistic-2.0", + "dependencies": { + "binaryextensions": "^6.11.0", + "editions": "^6.21.0", + "textextensions": "^6.11.0" + }, + "engines": { + "node": ">=4" + }, + "funding": { + "url": "https://bevry.me/fund" + } + }, + "node_modules/jackspeak": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-4.2.3.tgz", + "integrity": "sha512-ykkVRwrYvFm1nb2AJfKKYPr0emF6IiXDYUaFx4Zn9ZuIH7MrzEZ3sD5RlqGXNRpHtvUHJyOnCEFxOlNDtGo7wg==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^9.0.0" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/jest": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest/-/jest-29.7.0.tgz", + "integrity": "sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/core": "^29.7.0", + "@jest/types": "^29.6.3", + "import-local": "^3.0.2", + "jest-cli": "^29.7.0" + }, + "bin": { + "jest": "bin/jest.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/jest-changed-files": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-29.7.0.tgz", + "integrity": "sha512-fEArFiwf1BpQ+4bXSprcDc3/x4HSzL4al2tozwVpDFpsxALjLYdyiIK4e5Vz66GQJIbXJ82+35PtysofptNX2w==", + "dev": true, + "license": "MIT", + "dependencies": { + "execa": "^5.0.0", + "jest-util": "^29.7.0", + "p-limit": "^3.1.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-circus": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-29.7.0.tgz", + "integrity": "sha512-3E1nCMgipcTkCocFwM90XXQab9bS+GMsjdpmPrlelaxwD93Ad8iVEjX/vvHPdLPnFf+L40u+5+iutRdA1N9myw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/expect": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "co": "^4.6.0", + "dedent": "^1.0.0", + "is-generator-fn": "^2.0.0", + "jest-each": "^29.7.0", + "jest-matcher-utils": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-runtime": "^29.7.0", + "jest-snapshot": "^29.7.0", + "jest-util": "^29.7.0", + "p-limit": "^3.1.0", + "pretty-format": "^29.7.0", + "pure-rand": "^6.0.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-circus/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/jest-circus/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/jest-circus/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/jest-cli": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-29.7.0.tgz", + "integrity": "sha512-OVVobw2IubN/GSYsxETi+gOe7Ka59EFMR/twOU3Jb2GnKKeMGJB5SGUUrEz3SFVmJASUdZUzy83sLNNQ2gZslg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/core": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/types": "^29.6.3", + "chalk": "^4.0.0", + "create-jest": "^29.7.0", + "exit": "^0.1.2", + "import-local": "^3.0.2", + "jest-config": "^29.7.0", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "yargs": "^17.3.1" + }, + "bin": { + "jest": "bin/jest.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/jest-config": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-29.7.0.tgz", + "integrity": "sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.11.6", + "@jest/test-sequencer": "^29.7.0", + "@jest/types": "^29.6.3", + "babel-jest": "^29.7.0", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "deepmerge": "^4.2.2", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "jest-circus": "^29.7.0", + "jest-environment-node": "^29.7.0", + "jest-get-type": "^29.6.3", + "jest-regex-util": "^29.6.3", + "jest-resolve": "^29.7.0", + "jest-runner": "^29.7.0", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "micromatch": "^4.0.4", + "parse-json": "^5.2.0", + "pretty-format": "^29.7.0", + "slash": "^3.0.0", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@types/node": "*", + "ts-node": ">=9.0.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "ts-node": { + "optional": true + } + } + }, + "node_modules/jest-config/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/jest-config/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/jest-config/node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/jest-config/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "dev": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/jest-config/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/jest-config/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/jest-config/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/jest-diff": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.7.0.tgz", + "integrity": "sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.0.0", + "diff-sequences": "^29.6.3", + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-diff/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/jest-diff/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/jest-diff/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/jest-docblock": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-29.7.0.tgz", + "integrity": "sha512-q617Auw3A612guyaFgsbFeYpNP5t2aoUNLwBUbc/0kD1R4t9ixDbyFTHd1nok4epoVFpr7PmeWHrhvuV3XaJ4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "detect-newline": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-each": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-29.7.0.tgz", + "integrity": "sha512-gns+Er14+ZrEoC5fhOfYCY1LOHHr0TI+rQUHZS8Ttw2l7gl+80eHc/gFf2Ktkw0+SIACDTeWvpFcv3B04VembQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "chalk": "^4.0.0", + "jest-get-type": "^29.6.3", + "jest-util": "^29.7.0", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-each/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/jest-each/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/jest-each/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/jest-environment-jsdom": { + "version": "29.7.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/fake-timers": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/jsdom": "^20.0.0", + "@types/node": "*", + "jest-mock": "^29.7.0", + "jest-util": "^29.7.0", + "jsdom": "^20.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "canvas": "^2.5.0" + }, + "peerDependenciesMeta": { + "canvas": { + "optional": true + } + } + }, + "node_modules/jest-environment-node": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-29.7.0.tgz", + "integrity": "sha512-DOSwCRqXirTOyheM+4d5YZOrWcdu0LNZ87ewUoywbcb2XR4wKgqiG8vNeYwhjFMbEkfju7wx2GYH0P2gevGvFw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/fake-timers": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "jest-mock": "^29.7.0", + "jest-util": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-get-type": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.6.3.tgz", + "integrity": "sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-haste-map": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.7.0.tgz", + "integrity": "sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "@types/graceful-fs": "^4.1.3", + "@types/node": "*", + "anymatch": "^3.0.3", + "fb-watchman": "^2.0.0", + "graceful-fs": "^4.2.9", + "jest-regex-util": "^29.6.3", + "jest-util": "^29.7.0", + "jest-worker": "^29.7.0", + "micromatch": "^4.0.4", + "walker": "^1.0.8" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "optionalDependencies": { + "fsevents": "^2.3.2" + } + }, + "node_modules/jest-leak-detector": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-29.7.0.tgz", + "integrity": "sha512-kYA8IJcSYtST2BY9I+SMC32nDpBT3J2NvWJx8+JCuCdl/CR1I4EKUJROiP8XtCcxqgTTBGJNdbB1A8XRKbTetw==", + "dev": true, + "license": "MIT", + "dependencies": { + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-leak-detector/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/jest-leak-detector/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/jest-leak-detector/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/jest-matcher-utils": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.7.0.tgz", + "integrity": "sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.0.0", + "jest-diff": "^29.7.0", + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-matcher-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/jest-matcher-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/jest-matcher-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/jest-message-util": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.7.0.tgz", + "integrity": "sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.12.13", + "@jest/types": "^29.6.3", + "@types/stack-utils": "^2.0.0", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "micromatch": "^4.0.4", + "pretty-format": "^29.7.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-message-util/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/jest-message-util/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/jest-message-util/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/jest-mock": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-29.7.0.tgz", + "integrity": "sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "jest-util": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-pnp-resolver": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz", + "integrity": "sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + }, + "peerDependencies": { + "jest-resolve": "*" + }, + "peerDependenciesMeta": { + "jest-resolve": { + "optional": true + } + } + }, + "node_modules/jest-regex-util": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.6.3.tgz", + "integrity": "sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-resolve": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-29.7.0.tgz", + "integrity": "sha512-IOVhZSrg+UvVAshDSDtHyFCCBUl/Q3AAJv8iZ6ZjnZ74xzvwuzLXid9IIIPgTnY62SJjfuupMKZsZQRsCvxEgA==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "jest-pnp-resolver": "^1.2.2", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "resolve": "^1.20.0", + "resolve.exports": "^2.0.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-resolve-dependencies": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-29.7.0.tgz", + "integrity": "sha512-un0zD/6qxJ+S0et7WxeI3H5XSe9lTBBR7bOHCHXkKR6luG5mwDDlIzVQ0V5cZCuoTgEdcdwzTghYkTWfubi+nA==", + "dev": true, + "license": "MIT", + "dependencies": { + "jest-regex-util": "^29.6.3", + "jest-snapshot": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-runner": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-29.7.0.tgz", + "integrity": "sha512-fsc4N6cPCAahybGBfTRcq5wFR6fpLznMg47sY5aDpsoejOcVYFb07AHuSnR0liMcPTgBsA3ZJL6kFOjPdoNipQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/console": "^29.7.0", + "@jest/environment": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "emittery": "^0.13.1", + "graceful-fs": "^4.2.9", + "jest-docblock": "^29.7.0", + "jest-environment-node": "^29.7.0", + "jest-haste-map": "^29.7.0", + "jest-leak-detector": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-resolve": "^29.7.0", + "jest-runtime": "^29.7.0", + "jest-util": "^29.7.0", + "jest-watcher": "^29.7.0", + "jest-worker": "^29.7.0", + "p-limit": "^3.1.0", + "source-map-support": "0.5.13" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-runtime": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-29.7.0.tgz", + "integrity": "sha512-gUnLjgwdGqW7B4LvOIkbKs9WGbn+QLqRQQ9juC6HndeDiezIwhDP+mhMwHWCEcfQ5RUXa6OPnFF8BJh5xegwwQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/fake-timers": "^29.7.0", + "@jest/globals": "^29.7.0", + "@jest/source-map": "^29.6.3", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "cjs-module-lexer": "^1.0.0", + "collect-v8-coverage": "^1.0.0", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-mock": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-resolve": "^29.7.0", + "jest-snapshot": "^29.7.0", + "jest-util": "^29.7.0", + "slash": "^3.0.0", + "strip-bom": "^4.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-runtime/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/jest-runtime/node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/jest-runtime/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "dev": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/jest-runtime/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/jest-snapshot": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-29.7.0.tgz", + "integrity": "sha512-Rm0BMWtxBcioHr1/OX5YCP8Uov4riHvKPknOGs804Zg9JGZgmIBkbtlxJC/7Z4msKYVbIJtfU+tKb8xlYNfdkw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.11.6", + "@babel/generator": "^7.7.2", + "@babel/plugin-syntax-jsx": "^7.7.2", + "@babel/plugin-syntax-typescript": "^7.7.2", + "@babel/types": "^7.3.3", + "@jest/expect-utils": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "babel-preset-current-node-syntax": "^1.0.0", + "chalk": "^4.0.0", + "expect": "^29.7.0", + "graceful-fs": "^4.2.9", + "jest-diff": "^29.7.0", + "jest-get-type": "^29.6.3", + "jest-matcher-utils": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0", + "natural-compare": "^1.4.0", + "pretty-format": "^29.7.0", + "semver": "^7.5.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-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==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-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==", + "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/jest-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/jest-util": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz", + "integrity": "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-validate": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-29.7.0.tgz", + "integrity": "sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "camelcase": "^6.2.0", + "chalk": "^4.0.0", + "jest-get-type": "^29.6.3", + "leven": "^3.1.0", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-validate/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/jest-validate/node_modules/camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/jest-validate/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/jest-validate/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/jest-watcher": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-29.7.0.tgz", + "integrity": "sha512-49Fg7WXkU3Vl2h6LbLtMQ/HyB6rXSIX7SqvBLQmssRBGN9I0PNvPmAmCWSOY6SOvrjhI/F7/bGAv9RtnsPA03g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/test-result": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "ansi-escapes": "^4.2.1", + "chalk": "^4.0.0", + "emittery": "^0.13.1", + "jest-util": "^29.7.0", + "string-length": "^4.0.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-worker": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.7.0.tgz", + "integrity": "sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "jest-util": "^29.7.0", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-worker/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "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==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsdom": { + "version": "20.0.3", + "dev": true, + "license": "MIT", + "dependencies": { + "abab": "^2.0.6", + "acorn": "^8.8.1", + "acorn-globals": "^7.0.0", + "cssom": "^0.5.0", + "cssstyle": "^2.3.0", + "data-urls": "^3.0.2", + "decimal.js": "^10.4.2", + "domexception": "^4.0.0", + "escodegen": "^2.0.0", + "form-data": "^4.0.0", + "html-encoding-sniffer": "^3.0.0", + "http-proxy-agent": "^5.0.0", + "https-proxy-agent": "^5.0.1", + "is-potential-custom-element-name": "^1.0.1", + "nwsapi": "^2.2.2", + "parse5": "^7.1.1", + "saxes": "^6.0.0", + "symbol-tree": "^3.2.4", + "tough-cookie": "^4.1.2", + "w3c-xmlserializer": "^4.0.0", + "webidl-conversions": "^7.0.0", + "whatwg-encoding": "^2.0.0", + "whatwg-mimetype": "^3.0.0", + "whatwg-url": "^11.0.0", + "ws": "^8.11.0", + "xml-name-validator": "^4.0.0" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "canvas": "^2.5.0" + }, + "peerDependenciesMeta": { + "canvas": { + "optional": true + } + } + }, + "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", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true, + "license": "MIT" + }, + "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/jsonc-parser": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.3.1.tgz", + "integrity": "sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/jsonfile": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz", + "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/jsonfile/node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/jsonwebtoken": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.3.tgz", + "integrity": "sha512-MT/xP0CrubFRNLNKvxJ2BYfy53Zkm++5bX9dtuPbqAeQpTVe0MQTFhao8+Cp//EmJp244xt6Drw/GVEGCUj40g==", + "dev": true, + "license": "MIT", + "dependencies": { + "jws": "^4.0.1", + "lodash.includes": "^4.3.0", + "lodash.isboolean": "^3.0.3", + "lodash.isinteger": "^4.0.4", + "lodash.isnumber": "^3.0.3", + "lodash.isplainobject": "^4.0.6", + "lodash.isstring": "^4.0.1", + "lodash.once": "^4.0.0", + "ms": "^2.1.1", + "semver": "^7.5.4" + }, + "engines": { + "node": ">=12", + "npm": ">=6" + } + }, + "node_modules/jwa": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.1.tgz", + "integrity": "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-equal-constant-time": "^1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/jws": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.1.tgz", + "integrity": "sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA==", + "dev": true, + "license": "MIT", + "dependencies": { + "jwa": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/keytar": { + "version": "7.9.0", + "resolved": "https://registry.npmjs.org/keytar/-/keytar-7.9.0.tgz", + "integrity": "sha512-VPD8mtVtm5JNtA2AErl6Chp06JBfy7diFQ7TQQhdpWOl6MrCRB+eRbvAZUsbGQS9kiMq0coJsy0W0vHpDCkWsQ==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "dependencies": { + "node-addon-api": "^4.3.0", + "prebuild-install": "^7.0.1" + } + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/kleur": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", + "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/leven": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", + "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true, + "license": "MIT" + }, + "node_modules/linkify-it": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-5.0.0.tgz", + "integrity": "sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "uc.micro": "^2.0.0" + } + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash": { + "version": "4.17.23", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz", + "integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.includes": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", + "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.isboolean": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", + "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.isinteger": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", + "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.isnumber": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", + "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.isstring": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", + "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.memoize": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", + "integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.once": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", + "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.truncate": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/lodash.truncate/-/lodash.truncate-4.4.2.tgz", + "integrity": "sha512-jttmRe7bRse52OsWIMDLaXxWqRAmtIUccAQ3garviCqJjafXOfNMO0yMfNpdD6zbGaTU0P5Nz7e7gAT6cKmJRw==", + "dev": true, + "license": "MIT" + }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "license": "MIT", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/lz-string": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.5.0.tgz", + "integrity": "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==", + "dev": true, + "license": "MIT", + "peer": true, + "bin": { + "lz-string": "bin/bin.js" + } + }, + "node_modules/make-dir": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", + "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "dev": true, + "license": "ISC" + }, + "node_modules/makeerror": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", + "integrity": "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "tmpl": "1.0.5" + } + }, + "node_modules/markdown-it": { + "version": "14.1.1", + "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-14.1.1.tgz", + "integrity": "sha512-BuU2qnTti9YKgK5N+IeMubp14ZUKUUw7yeJbkjtosvHiP0AZ5c8IAgEMk79D0eC8F23r4Ac/q8cAIFdm2FtyoA==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1", + "entities": "^4.4.0", + "linkify-it": "^5.0.0", + "mdurl": "^2.0.0", + "punycode.js": "^2.3.1", + "uc.micro": "^2.1.0" + }, + "bin": { + "markdown-it": "bin/markdown-it.mjs" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/mdurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-2.0.0.tgz", + "integrity": "sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==", + "dev": true, + "license": "MIT" + }, + "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": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "dev": true, + "license": "MIT", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/mimic-response": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", + "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=10" + }, + "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", + "integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/minimatch": { + "version": "10.2.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.4.tgz", + "integrity": "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/minipass": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", + "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/mkdirp-classic": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", + "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/mute-stream": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", + "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==", + "dev": true, + "license": "ISC" + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/napi-build-utils": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-2.0.0.tgz", + "integrity": "sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA==", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true, + "license": "MIT" + }, + "node_modules/neo-async": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", + "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", + "dev": true, + "license": "MIT" + }, + "node_modules/node-abi": { + "version": "3.87.0", + "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.87.0.tgz", + "integrity": "sha512-+CGM1L1CgmtheLcBuleyYOn7NWPVu0s0EJH2C4puxgEZb9h8QpR9G2dBfZJOAUhi7VQxuBPMd0hiISWcTyiYyQ==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "semver": "^7.3.5" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/node-addon-api": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-4.3.0.tgz", + "integrity": "sha512-73sE9+3UaLYYFmDsFZnqCInzPyh3MqIwZO9cw58yIqAZhONrrabrYyYe3TuIqtIiOuTXVhsGau8hcrhhwSsDIQ==", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/node-int64": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", + "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/node-releases": { + "version": "2.0.36", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.36.tgz", + "integrity": "sha512-TdC8FSgHz8Mwtw9g5L4gR/Sh9XhSP/0DEkQxfEFXOpiul5IiHgHan2VhYYb6agDSfp4KuvltmGApc8HMgUrIkA==", + "dev": true, + "license": "MIT" + }, + "node_modules/node-sarif-builder": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/node-sarif-builder/-/node-sarif-builder-3.4.0.tgz", + "integrity": "sha512-tGnJW6OKRii9u/b2WiUViTJS+h7Apxx17qsMUjsUeNDiMMX5ZFf8F8Fcz7PAQ6omvOxHZtvDTmOYKJQwmfpjeg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/sarif": "^2.1.7", + "fs-extra": "^11.1.1" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/normalize-package-data": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-6.0.2.tgz", + "integrity": "sha512-V6gygoYb/5EmNI+MEGrWkC+e6+Rr7mTmfHrxDbLzxQogBkgzo76rkok0Am6thgSF7Mv2nLOajAJj5vDJZEFn7g==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "hosted-git-info": "^7.0.0", + "semver": "^7.3.5", + "validate-npm-package-license": "^3.0.4" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/normalize-package-data/node_modules/hosted-git-info": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-7.0.2.tgz", + "integrity": "sha512-puUZAUKT5m8Zzvs72XWy3HtvVbTWljRE66cP60bxJzAqf2DgICo7lYTY2IHUmLnNpjYvw5bvmoHvPc0QO2a62w==", + "dev": true, + "license": "ISC", + "dependencies": { + "lru-cache": "^10.0.1" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/normalize-package-data/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/nth-check": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", + "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "boolbase": "^1.0.0" + }, + "funding": { + "url": "https://github.com/fb55/nth-check?sponsor=1" + } + }, + "node_modules/nwsapi": { + "version": "2.2.23", + "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.23.tgz", + "integrity": "sha512-7wfH4sLbt4M0gCDzGE6vzQBo0bfTKjU7Sfpqy/7gs1qBfYz2vEJH6vXcBKpO3+6Yu1telwd0t9HpyOoLEQQbIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/open": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/open/-/open-10.2.0.tgz", + "integrity": "sha512-YgBpdJHPyQ2UE5x+hlSXcnejzAvD0b22U2OuAP+8OnlJT+PjWPxtgmGqKKc+RgTM63U9gN0YzrYc71R2WT/hTA==", + "dev": true, + "license": "MIT", + "dependencies": { + "default-browser": "^5.2.1", + "define-lazy-prop": "^3.0.0", + "is-inside-container": "^1.0.0", + "wsl-utils": "^0.1.0" + }, + "engines": { + "node": ">=18" + }, + "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", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-map": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-7.0.4.tgz", + "integrity": "sha512-tkAQEw8ysMzmkhgw8k+1U/iPhWNhykKnSk4Rd5zLoPJCuJaGRPo6YposrZgaxHKzDHdDWWZvE/Sk7hsL2X/CpQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/package-json-from-dist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", + "dev": true, + "license": "BlueOak-1.0.0" + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parse-semver": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/parse-semver/-/parse-semver-1.1.1.tgz", + "integrity": "sha512-Eg1OuNntBMH0ojvEKSrvDSnwLmvVuUOSdylH/pSCPNMIspLlweJyIWXCE+k/5hm3cj/EBUYwmWkjhBALNP4LXQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^5.1.0" + } + }, + "node_modules/parse-semver/node_modules/semver": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/parse5": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz", + "integrity": "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==", + "dev": true, + "license": "MIT", + "dependencies": { + "entities": "^6.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/parse5-htmlparser2-tree-adapter": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-7.1.0.tgz", + "integrity": "sha512-ruw5xyKs6lrpo9x9rCZqZZnIUntICjQAd0Wsmp396Ul9lN/h+ifgVV1x1gZHi8euej6wTfpqX8j+BFQxF0NS/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "domhandler": "^5.0.3", + "parse5": "^7.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/parse5-parser-stream": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/parse5-parser-stream/-/parse5-parser-stream-7.1.2.tgz", + "integrity": "sha512-JyeQc9iwFLn5TbvvqACIF/VXG6abODeB3Fwmv/TGdLk2LfbWkaySGY72at4+Ty7EkPZj854u4CrICqNk2qIbow==", + "dev": true, + "license": "MIT", + "dependencies": { + "parse5": "^7.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/parse5/node_modules/entities": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", + "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true, + "license": "MIT" + }, + "node_modules/path-scurry": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.2.tgz", + "integrity": "sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^11.0.0", + "minipass": "^7.1.2" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/path-scurry/node_modules/lru-cache": { + "version": "11.2.6", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.6.tgz", + "integrity": "sha512-ESL2CrkS/2wTPfuend7Zhkzo2u0daGJ/A2VucJOgQ/C48S/zB8MMeMHSGKYpXhIjbPxfuezITkaBH1wqv00DDQ==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/path-type": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-6.0.0.tgz", + "integrity": "sha512-Vj7sf++t5pBD637NSfkxpHSMfWaeig5+DKWLhcqIYx6mWQz5hdJTGDVMQiJcw1ZYkhs7AazKDGpRVji1LJCZUQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pend": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", + "integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==", + "dev": true, + "license": "MIT" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pirates": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz", + "integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "find-up": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-dir/node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-dir/node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-dir/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pkg-dir/node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pluralize": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-8.0.0.tgz", + "integrity": "sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss": { + "version": "8.5.8", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.8.tgz", + "integrity": "sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/prebuild-install": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.3.tgz", + "integrity": "sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug==", + "deprecated": "No longer maintained. Please contact the author of the relevant native addon; alternatives are available.", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "detect-libc": "^2.0.0", + "expand-template": "^2.0.3", + "github-from-package": "0.0.0", + "minimist": "^1.2.3", + "mkdirp-classic": "^0.5.3", + "napi-build-utils": "^2.0.0", + "node-abi": "^3.3.0", + "pump": "^3.0.0", + "rc": "^1.2.7", + "simple-get": "^4.0.0", + "tar-fs": "^2.0.0", + "tunnel-agent": "^0.6.0" + }, + "bin": { + "prebuild-install": "bin.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/prettier": { + "version": "3.8.1", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.8.1.tgz", + "integrity": "sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg==", + "dev": true, + "license": "MIT", + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/pretty-format": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz", + "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "ansi-regex": "^5.0.1", + "ansi-styles": "^5.0.0", + "react-is": "^17.0.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/pretty-format/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", + "peer": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/prompts": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", + "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "kleur": "^3.0.3", + "sisteransi": "^1.0.5" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/psl": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.15.0.tgz", + "integrity": "sha512-JZd3gMVBAVQkSs6HdNZo9Sdo0LNcQeMNP3CozBJb3JYC/QUYZTnKxP+f8oWRX4rHP5EurWxqAHTSwUCjlNKa1w==", + "dev": true, + "license": "MIT", + "dependencies": { + "punycode": "^2.3.1" + }, + "funding": { + "url": "https://github.com/sponsors/lupomontero" + } + }, + "node_modules/pump": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.4.tgz", + "integrity": "sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/punycode.js": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode.js/-/punycode.js-2.3.1.tgz", + "integrity": "sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/pure-rand": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.1.0.tgz", + "integrity": "sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/dubzzz" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fast-check" + } + ], + "license": "MIT" + }, + "node_modules/qs": { + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.0.tgz", + "integrity": "sha512-mAZTtNCeetKMH+pSjrb76NAM8V9a05I9aBZOHztWy/UqcJdQYNsf59vrRKWnojAT9Y+GbIvoTBC++CPHqpDBhQ==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/querystringify": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", + "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/rc": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", + "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", + "dev": true, + "license": "(BSD-2-Clause OR MIT OR Apache-2.0)", + "optional": true, + "dependencies": { + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + }, + "bin": { + "rc": "cli.js" + } + }, + "node_modules/rc-config-loader": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/rc-config-loader/-/rc-config-loader-4.1.4.tgz", + "integrity": "sha512-3GiwEzklkbXTDp52UR5nT8iXgYAx1V9ZG/kDZT7p60u2GCv2XTwQq4NzinMoMpNtXhmt3WkhYXcj6HH8HdwCEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.4.3", + "js-yaml": "^4.1.1", + "json5": "^2.2.3", + "require-from-string": "^2.0.2" + } + }, + "node_modules/rc/node_modules/strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", + "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", + "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0", + "scheduler": "^0.23.2" + }, + "peerDependencies": { + "react": "^18.3.1" + } + }, + "node_modules/react-is": { + "version": "17.0.2", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", + "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/react-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/read": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/read/-/read-1.0.7.tgz", + "integrity": "sha512-rSOKNYUmaxy0om1BNjMN4ezNT6VKK+2xF4GBhc81mkH7L60i6dp8qPYrkndNLT3QPphoII3maL9PVC9XmhHwVQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "mute-stream": "~0.0.4" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/read-pkg": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-9.0.1.tgz", + "integrity": "sha512-9viLL4/n1BJUCT1NXVTdS1jtm80yDEgR5T4yCelII49Mbj0v1rZdKqj7zCiYdbB0CuCgdrvHcNogAKTFPBocFA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/normalize-package-data": "^2.4.3", + "normalize-package-data": "^6.0.0", + "parse-json": "^8.0.0", + "type-fest": "^4.6.0", + "unicorn-magic": "^0.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/read-pkg/node_modules/parse-json": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-8.3.0.tgz", + "integrity": "sha512-ybiGyvspI+fAoRQbIPRddCcSTV9/LsJbf0e/S85VLowVGzRmokfneg2kwVW/KU5rOXrPSbF1qAKPMgNTqqROQQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.26.2", + "index-to-position": "^1.1.0", + "type-fest": "^4.39.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/read-pkg/node_modules/type-fest": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz", + "integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/read-pkg/node_modules/unicorn-magic": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.1.0.tgz", + "integrity": "sha512-lRfVq8fE8gz6QMBuDM6a+LO3IAzTi05H6gCVaUpir2E1Rwpo4ZUog45KpNXKC/Mn3Yb9UDuHumeFTo9iV/D9FQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/redent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz", + "integrity": "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==", + "dev": true, + "license": "MIT", + "dependencies": { + "indent-string": "^4.0.0", + "strip-indent": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/requires-port": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", + "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/resolve": { + "version": "1.22.11", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz", + "integrity": "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-core-module": "^2.16.1", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-cwd": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", + "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve-cwd/node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/resolve.exports": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-2.0.3.tgz", + "integrity": "sha512-OcXjMsGdhL4XnbShKpAcSqPMzQoYkYyhbEaeSko47MjRP9NfEQMhZkXL1DoFlt9LWQn4YttrdnV6X2OiyzBi+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/reusify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "dev": true, + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rollup": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.59.0.tgz", + "integrity": "sha512-2oMpl67a3zCH9H79LeMcbDhXW/UmWG/y2zuqnF2jQq5uq9TbM9TVyXvA4+t+ne2IIkBdrLpAaRQAvo7YI/Yyeg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.8" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.59.0", + "@rollup/rollup-android-arm64": "4.59.0", + "@rollup/rollup-darwin-arm64": "4.59.0", + "@rollup/rollup-darwin-x64": "4.59.0", + "@rollup/rollup-freebsd-arm64": "4.59.0", + "@rollup/rollup-freebsd-x64": "4.59.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.59.0", + "@rollup/rollup-linux-arm-musleabihf": "4.59.0", + "@rollup/rollup-linux-arm64-gnu": "4.59.0", + "@rollup/rollup-linux-arm64-musl": "4.59.0", + "@rollup/rollup-linux-loong64-gnu": "4.59.0", + "@rollup/rollup-linux-loong64-musl": "4.59.0", + "@rollup/rollup-linux-ppc64-gnu": "4.59.0", + "@rollup/rollup-linux-ppc64-musl": "4.59.0", + "@rollup/rollup-linux-riscv64-gnu": "4.59.0", + "@rollup/rollup-linux-riscv64-musl": "4.59.0", + "@rollup/rollup-linux-s390x-gnu": "4.59.0", + "@rollup/rollup-linux-x64-gnu": "4.59.0", + "@rollup/rollup-linux-x64-musl": "4.59.0", + "@rollup/rollup-openbsd-x64": "4.59.0", + "@rollup/rollup-openharmony-arm64": "4.59.0", + "@rollup/rollup-win32-arm64-msvc": "4.59.0", + "@rollup/rollup-win32-ia32-msvc": "4.59.0", + "@rollup/rollup-win32-x64-gnu": "4.59.0", + "@rollup/rollup-win32-x64-msvc": "4.59.0", + "fsevents": "~2.3.2" + } + }, + "node_modules/run-applescript": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/run-applescript/-/run-applescript-7.1.0.tgz", + "integrity": "sha512-DPe5pVFaAsinSaV6QjQ6gdiedWDcRCbUuiQfQa2wmWV7+xC9bGulGI8+TdRmoFkAPaBXk8CrAbnlY2ISniJ47Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/rxjs": { + "version": "7.8.2", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz", + "integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.1.0" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true, + "license": "MIT" + }, + "node_modules/sax": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.5.0.tgz", + "integrity": "sha512-21IYA3Q5cQf089Z6tgaUTr7lDAyzoTPx5HRtbhsME8Udispad8dC/+sziTNugOEx54ilvatQ9YCzl4KQLPcRHA==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=11.0.0" + } + }, + "node_modules/saxes": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz", + "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==", + "dev": true, + "license": "ISC", + "dependencies": { + "xmlchars": "^2.2.0" + }, + "engines": { + "node": ">=v12.22.7" + } + }, + "node_modules/scheduler": { + "version": "0.23.2", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", + "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0" + } + }, + "node_modules/secretlint": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/secretlint/-/secretlint-10.2.2.tgz", + "integrity": "sha512-xVpkeHV/aoWe4vP4TansF622nBEImzCY73y/0042DuJ29iKIaqgoJ8fGxre3rVSHHbxar4FdJobmTnLp9AU0eg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@secretlint/config-creator": "^10.2.2", + "@secretlint/formatter": "^10.2.2", + "@secretlint/node": "^10.2.2", + "@secretlint/profiler": "^10.2.2", + "debug": "^4.4.1", + "globby": "^14.1.0", + "read-pkg": "^9.0.1" + }, + "bin": { + "secretlint": "bin/secretlint.js" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/shell-quote": { + "version": "1.8.3", + "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.3.tgz", + "integrity": "sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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/simple-concat": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz", + "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "optional": true + }, + "node_modules/simple-get": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-4.0.1.tgz", + "integrity": "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "optional": true, + "dependencies": { + "decompress-response": "^6.0.0", + "once": "^1.3.1", + "simple-concat": "^1.0.0" + } + }, + "node_modules/sisteransi": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", + "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", + "dev": true, + "license": "MIT" + }, + "node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/slice-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz", + "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "astral-regex": "^2.0.0", + "is-fullwidth-code-point": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/slice-ansi?sponsor=1" + } + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-support": { + "version": "0.5.13", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz", + "integrity": "sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/spawn-command": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/spawn-command/-/spawn-command-0.0.2.tgz", + "integrity": "sha512-zC8zGoGkmc8J9ndvml8Xksr1Amk9qBujgbF0JAIWO7kXr43w0h/0GJNM/Vustixu+YE8N/MTrQ7N31FvHUACxQ==", + "dev": true + }, + "node_modules/spdx-correct": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.2.0.tgz", + "integrity": "sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/spdx-exceptions": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.5.0.tgz", + "integrity": "sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w==", + "dev": true, + "license": "CC-BY-3.0" + }, + "node_modules/spdx-expression-parse": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", + "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/spdx-license-ids": { + "version": "3.0.23", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.23.tgz", + "integrity": "sha512-CWLcCCH7VLu13TgOH+r8p1O/Znwhqv/dbb6lqWy67G+pT1kHmeD/+V36AVb/vq8QMIQwVShJ6Ssl5FPh0fuSdw==", + "dev": true, + "license": "CC0-1.0" + }, + "node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/stack-utils": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", + "integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "escape-string-regexp": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/stack-utils/node_modules/escape-string-regexp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", + "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/string-length": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", + "integrity": "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "char-regex": "^1.0.2", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/string-length/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.2.2" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/strip-ansi/node_modules/ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/strip-bom": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", + "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-final-newline": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/strip-indent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz", + "integrity": "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "min-indent": "^1.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/structured-source": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/structured-source/-/structured-source-4.0.0.tgz", + "integrity": "sha512-qGzRFNJDjFieQkl/sVOI2dUjHKRyL9dAJi2gCPGJLbJHBIkyOHxjuocpIEfbLioX+qSJpvbYdT49/YCdMznKxA==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "boundary": "^2.0.0" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-hyperlinks": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-3.2.0.tgz", + "integrity": "sha512-zFObLMyZeEwzAoKCyu1B91U79K2t7ApXuQfo8OuxwXLDgcKxuwM+YvcbIhm6QWqz7mHUH1TVytR1PwVVjEuMig==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0", + "supports-color": "^7.0.0" + }, + "engines": { + "node": ">=14.18" + }, + "funding": { + "url": "https://github.com/chalk/supports-hyperlinks?sponsor=1" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/symbol-tree": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", + "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", + "dev": true, + "license": "MIT" + }, + "node_modules/tabbable": { + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/tabbable/-/tabbable-5.3.3.tgz", + "integrity": "sha512-QD9qKY3StfbZqWOPLp0++pOrAVb/HbUi5xCc8cUo4XjP19808oaMiDzn0leBY5mCespIBM0CIZePzZjgzR83kA==", + "license": "MIT" + }, + "node_modules/table": { + "version": "6.9.0", + "resolved": "https://registry.npmjs.org/table/-/table-6.9.0.tgz", + "integrity": "sha512-9kY+CygyYM6j02t5YFHbNz2FN5QmYGv9zAjVp4lCDjlCw7amdckXlEt/bjMhUIfj4ThGRE4gCUH5+yGnNuPo5A==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "ajv": "^8.0.1", + "lodash.truncate": "^4.4.2", + "slice-ansi": "^4.0.0", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/table/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/tar-fs": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.4.tgz", + "integrity": "sha512-mDAjwmZdh7LTT6pNleZ05Yt65HC3E+NiQzl672vQG38jIrehtJk/J3mNwIg+vShQPcLF/LV7CMnDW6vjj6sfYQ==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "chownr": "^1.1.1", + "mkdirp-classic": "^0.5.2", + "pump": "^3.0.0", + "tar-stream": "^2.1.4" + } + }, + "node_modules/tar-stream": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", + "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "bl": "^4.0.3", + "end-of-stream": "^1.4.1", + "fs-constants": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/terminal-link": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/terminal-link/-/terminal-link-4.0.0.tgz", + "integrity": "sha512-lk+vH+MccxNqgVqSnkMVKx4VLJfnLjDBGzH16JVZjKE2DoxP57s6/vt6JmXV5I3jBcfGrxNrYtC+mPtU7WJztA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-escapes": "^7.0.0", + "supports-hyperlinks": "^3.2.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/terminal-link/node_modules/ansi-escapes": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-7.3.0.tgz", + "integrity": "sha512-BvU8nYgGQBxcmMuEeUEmNTvrMVjJNSH7RgW24vXexN4Ven6qCvy4TntnvlnwnMLTVlcRQQdbRY8NKnaIoeWDNg==", + "dev": true, + "license": "MIT", + "dependencies": { + "environment": "^1.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/test-exclude": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", + "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", + "dev": true, + "license": "ISC", + "dependencies": { + "@istanbuljs/schema": "^0.1.2", + "glob": "^7.1.4", + "minimatch": "^3.0.4" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/test-exclude/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/test-exclude/node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/test-exclude/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "dev": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/test-exclude/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", + "dev": true, + "license": "MIT" + }, + "node_modules/textextensions": { + "version": "6.11.0", + "resolved": "https://registry.npmjs.org/textextensions/-/textextensions-6.11.0.tgz", + "integrity": "sha512-tXJwSr9355kFJI3lbCkPpUH5cP8/M0GGy2xLO34aZCjMXBaK3SoPnZwr/oWmo1FdCnELcs4npdCIOFtq9W3ruQ==", + "dev": true, + "license": "Artistic-2.0", + "dependencies": { + "editions": "^6.21.0" + }, + "engines": { + "node": ">=4" + }, + "funding": { + "url": "https://bevry.me/fund" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.15", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", + "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.3" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tinyglobby/node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/tinyglobby/node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/tmp": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.5.tgz", + "integrity": "sha512-voyz6MApa1rQGUxT3E+BK7/ROe8itEx7vD8/HEvt4xwXucvQ5G5oeEiHkmHZJuBO21RpOf+YYm9MOivj709jow==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.14" + } + }, + "node_modules/tmpl": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", + "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/tough-cookie": { + "version": "4.1.4", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "psl": "^1.1.33", + "punycode": "^2.1.1", + "universalify": "^0.2.0", + "url-parse": "^1.5.3" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/tr46": { + "version": "3.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "punycode": "^2.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/tree-kill": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", + "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==", + "dev": true, + "license": "MIT", + "bin": { + "tree-kill": "cli.js" + } + }, + "node_modules/ts-api-utils": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.4.0.tgz", + "integrity": "sha512-3TaVTaAv2gTiMB35i3FiGJaRfwb3Pyn/j3m/bfAvGe8FB7CF6u+LMYqYlDh7reQf7UNvoTvdfAqHGmPGOSsPmA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.12" + }, + "peerDependencies": { + "typescript": ">=4.8.4" + } + }, + "node_modules/ts-jest": { + "version": "29.4.6", + "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.4.6.tgz", + "integrity": "sha512-fSpWtOO/1AjSNQguk43hb/JCo16oJDnMJf3CdEGNkqsEX3t0KX96xvyX1D7PfLCpVoKu4MfVrqUkFyblYoY4lA==", + "dev": true, + "license": "MIT", + "dependencies": { + "bs-logger": "^0.2.6", + "fast-json-stable-stringify": "^2.1.0", + "handlebars": "^4.7.8", + "json5": "^2.2.3", + "lodash.memoize": "^4.1.2", + "make-error": "^1.3.6", + "semver": "^7.7.3", + "type-fest": "^4.41.0", + "yargs-parser": "^21.1.1" + }, + "bin": { + "ts-jest": "cli.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || ^18.0.0 || >=20.0.0" + }, + "peerDependencies": { + "@babel/core": ">=7.0.0-beta.0 <8", + "@jest/transform": "^29.0.0 || ^30.0.0", + "@jest/types": "^29.0.0 || ^30.0.0", + "babel-jest": "^29.0.0 || ^30.0.0", + "jest": "^29.0.0 || ^30.0.0", + "jest-util": "^29.0.0 || ^30.0.0", + "typescript": ">=4.3 <6" + }, + "peerDependenciesMeta": { + "@babel/core": { + "optional": true + }, + "@jest/transform": { + "optional": true + }, + "@jest/types": { + "optional": true + }, + "babel-jest": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "jest-util": { + "optional": true + } + } + }, + "node_modules/ts-jest/node_modules/type-fest": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz", + "integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/tunnel": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/tunnel/-/tunnel-0.0.6.tgz", + "integrity": "sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.6.11 <=0.7.0 || >=0.7.3" + } + }, + "node_modules/tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", + "dev": true, + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "safe-buffer": "^5.0.1" + }, + "engines": { + "node": "*" + } + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/type-fest": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", + "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/typed-rest-client": { + "version": "1.8.11", + "resolved": "https://registry.npmjs.org/typed-rest-client/-/typed-rest-client-1.8.11.tgz", + "integrity": "sha512-5UvfMpd1oelmUPRbbaVnq+rHP7ng2cE4qoQkQeAqxRL6PklkxsM0g32/HL0yfvruK6ojQ5x8EE+HF4YV6DtuCA==", + "dev": true, + "license": "MIT", + "dependencies": { + "qs": "^6.9.1", + "tunnel": "0.0.6", + "underscore": "^1.12.1" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/uc.micro": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-2.1.0.tgz", + "integrity": "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==", + "dev": true, + "license": "MIT" + }, + "node_modules/uglify-js": { + "version": "3.19.3", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.19.3.tgz", + "integrity": "sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ==", + "dev": true, + "license": "BSD-2-Clause", + "optional": true, + "bin": { + "uglifyjs": "bin/uglifyjs" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/underscore": { + "version": "1.13.8", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.8.tgz", + "integrity": "sha512-DXtD3ZtEQzc7M8m4cXotyHR+FAS18C64asBYY5vqZexfYryNNnDc02W4hKg3rdQuqOYas1jkseX0+nZXjTXnvQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/undici": { + "version": "7.22.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-7.22.0.tgz", + "integrity": "sha512-RqslV2Us5BrllB+JeiZnK4peryVTndy9Dnqq62S3yYRRTj0tFQCwEniUy2167skdGOy3vqRzEvl1Dm4sV2ReDg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20.18.1" + } + }, + "node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/unicorn-magic": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.3.0.tgz", + "integrity": "sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/universalify": { + "version": "0.2.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", + "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/url-join": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/url-join/-/url-join-4.0.1.tgz", + "integrity": "sha512-jk1+QP6ZJqyOiuEI9AEWQfju/nB2Pw466kbA0LEZljHwKeMgd9WrAEgEGxjPDD2+TNbbb37rTyhEfrCXfuKXnA==", + "dev": true, + "license": "MIT" + }, + "node_modules/url-parse": { + "version": "1.5.10", + "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", + "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "querystringify": "^2.1.1", + "requires-port": "^1.0.0" + } + }, + "node_modules/use-sync-external-store": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz", + "integrity": "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==", + "license": "MIT", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/uuid": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/v8-to-istanbul": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz", + "integrity": "sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==", + "dev": true, + "license": "ISC", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.12", + "@types/istanbul-lib-coverage": "^2.0.1", + "convert-source-map": "^2.0.0" + }, + "engines": { + "node": ">=10.12.0" + } + }, + "node_modules/validate-npm-package-license": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", + "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" + } + }, + "node_modules/version-range": { + "version": "4.15.0", + "resolved": "https://registry.npmjs.org/version-range/-/version-range-4.15.0.tgz", + "integrity": "sha512-Ck0EJbAGxHwprkzFO966t4/5QkRuzh+/I1RxhLgUKKwEn+Cd8NwM60mE3AqBZg5gYODoXW0EFsQvbZjRlvdqbg==", + "dev": true, + "license": "Artistic-2.0", + "engines": { + "node": ">=4" + }, + "funding": { + "url": "https://bevry.me/fund" + } + }, + "node_modules/vite": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.1.tgz", + "integrity": "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.27.0", + "fdir": "^6.5.0", + "picomatch": "^4.0.3", + "postcss": "^8.5.6", + "rollup": "^4.43.0", + "tinyglobby": "^0.2.15" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "lightningcss": "^1.21.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 + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/vite/node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/vite/node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/w3c-xmlserializer": { + "version": "4.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "xml-name-validator": "^4.0.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/walker": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", + "integrity": "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "makeerror": "1.0.12" + } + }, + "node_modules/webidl-conversions": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", + "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + } + }, + "node_modules/whatwg-encoding": { + "version": "2.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "iconv-lite": "0.6.3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/whatwg-mimetype": { + "version": "3.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + } + }, + "node_modules/whatwg-url": { + "version": "11.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "tr46": "^3.0.0", + "webidl-conversions": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/wordwrap": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", + "integrity": "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/write-file-atomic": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-4.0.2.tgz", + "integrity": "sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==", + "dev": true, + "license": "ISC", + "dependencies": { + "imurmurhash": "^0.1.4", + "signal-exit": "^3.0.7" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/write-file-atomic/node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/ws": { + "version": "8.19.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.19.0.tgz", + "integrity": "sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/wsl-utils": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/wsl-utils/-/wsl-utils-0.1.0.tgz", + "integrity": "sha512-h3Fbisa2nKGPxCpm89Hk33lBLsnaGBvctQopaBSOW/uIs6FTe1ATyAnKFJrzVs9vpGdsTe73WF3V4lIsk4Gacw==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-wsl": "^3.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/xml-name-validator": { + "version": "4.0.0", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12" + } + }, + "node_modules/xml2js": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.5.0.tgz", + "integrity": "sha512-drPFnkQJik/O+uPKpqSgr22mpuFHqKdbS835iAQrUC73L2F5WkboIRd63ai/2Yg6I1jzifPFKH2NTK+cfglkIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "sax": ">=0.6.0", + "xmlbuilder": "~11.0.0" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/xmlbuilder": { + "version": "11.0.1", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz", + "integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/xmlchars": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", + "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", + "dev": true, + "license": "MIT" + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "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/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/yauzl": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", + "integrity": "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-crc32": "~0.2.3", + "fd-slicer": "~1.1.0" + } + }, + "node_modules/yazl": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/yazl/-/yazl-2.5.1.tgz", + "integrity": "sha512-phENi2PLiHnHb6QBVot+dJnaAZ0xosj7p3fWl+znIjBDlnMI2PsZCJZ306BPTFOaHf5qdDEI8x5qFrSOBN5vrw==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-crc32": "~0.2.3" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/zod": { + "version": "3.25.76", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", + "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/zustand": { + "version": "4.5.7", + "resolved": "https://registry.npmjs.org/zustand/-/zustand-4.5.7.tgz", + "integrity": "sha512-CHOUy7mu3lbD6o6LJLfllpjkzhHXSBlX8B9+qPddUsIfeF5S/UZ5q0kmCsnRqT1UHFQZchNFDDzMbQsuesHWlw==", + "license": "MIT", + "dependencies": { + "use-sync-external-store": "^1.2.2" + }, + "engines": { + "node": ">=12.7.0" + }, + "peerDependencies": { + "@types/react": ">=16.8", + "immer": ">=9.0.6", + "react": ">=16.8" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "immer": { + "optional": true + }, + "react": { + "optional": true + } + } + } + } +} diff --git a/packages/vscode-extension/package.json b/packages/vscode-extension/package.json index 25eb3b5..d6deff1 100644 --- a/packages/vscode-extension/package.json +++ b/packages/vscode-extension/package.json @@ -3,7 +3,7 @@ "displayName": "SchemaX", "description": "Declarative schema management for Databricks Unity Catalog. Visual designer, Git-based versioning, governance (grants, tags, row filters), and CI/CD-ready deployment across environments.", "publisher": "schematic-dev", - "version": "0.2.10", + "version": "0.2.11", "author": { "name": "Field Engineering" }, @@ -145,20 +145,20 @@ "@testing-library/jest-dom": "^6.9.1", "@testing-library/react": "^16.3.0", "@testing-library/user-event": "^14.6.1", - "@typescript-eslint/eslint-plugin": "^8.46.1", - "@typescript-eslint/parser": "^8.46.1", "@types/jest": "^29.5.0", "@types/node": "^20.11.0", "@types/react": "^18.2.0", "@types/react-dom": "^18.2.0", "@types/uuid": "^9.0.0", "@types/vscode": "^1.90.0", + "@typescript-eslint/eslint-plugin": "^8.46.1", + "@typescript-eslint/parser": "^8.46.1", "@vitejs/plugin-react": "^4.2.0", "@vscode/vsce": "^3.6.0", "concurrently": "^8.2.0", + "esbuild": "^0.27.0", "eslint": "^9.38.0", "eslint-config-prettier": "^10.1.8", - "esbuild": "^0.27.0", "jest": "^29.7.0", "jest-environment-jsdom": "^29.7.0", "prettier": "^3.6.2", @@ -168,6 +168,7 @@ }, "dependencies": { "@vscode/codicons": "^0.0.36", + "@vscode/python-extension": "^1.0.6", "@vscode/webview-ui-toolkit": "^1.4.0", "react": "^18.2.0", "react-dom": "^18.2.0", diff --git a/packages/vscode-extension/src/backend/pythonBackendClient.ts b/packages/vscode-extension/src/backend/pythonBackendClient.ts index 3fa3420..c8ca46a 100644 --- a/packages/vscode-extension/src/backend/pythonBackendClient.ts +++ b/packages/vscode-extension/src/backend/pythonBackendClient.ts @@ -1,5 +1,4 @@ import { spawn } from "child_process"; -import * as vscode from "vscode"; import type { CommandEnvelope, PythonCommandResult } from "./contracts"; type StreamHandler = (chunk: string) => void; @@ -23,23 +22,60 @@ const BASE_COMMAND_CANDIDATES: CommandCandidate[] = [ { cmd: "python", baseArgs: ["-m", "schemax.cli"], useShell: true }, ]; +/** + * Resolve the Python executable path from the ms-python.python extension API. + * + * Uses the official @vscode/python-extension API which correctly handles all + * environment types (conda, micromamba, venv, poetry, uv, pyenv, etc.) and + * respects the user's interpreter selection in VS Code — not just the + * defaultInterpreterPath setting. + * + * Returns undefined if the Python extension is not installed or the path + * cannot be resolved. Callers should fall back to other detection methods. + */ +async function getPythonExtensionInterpreterPath(): Promise { + try { + const { PythonExtension } = await import("@vscode/python-extension"); + const api = await PythonExtension.api(); + const envPath = api.environments.getActiveEnvironmentPath(); + const resolved = await api.environments.resolveEnvironment(envPath); + if (resolved?.executable?.uri) { + return resolved.executable.uri.fsPath; + } + // envPath.path may be a folder (environment root) — resolve to executable + if (envPath?.path) { + const fs = await import("fs"); + const path = await import("path"); + const candidate = path.join(envPath.path, "bin", "python"); + if (fs.existsSync(candidate)) { + return candidate; + } + // On Windows, check Scripts/python.exe + const winCandidate = path.join(envPath.path, "Scripts", "python.exe"); + if (fs.existsSync(winCandidate)) { + return winCandidate; + } + } + } catch { + // Python extension not installed or not activated — fall through. + } + return undefined; +} + /** * Build the ordered list of command candidates. - * If the user (or VS Code Python extension) has configured an interpreter path, - * prepend it so that venv / uv / poetry / conda envs selected in VS Code are - * tried first — before falling back to PATH-based lookup. * - * Configured interpreter paths are spawned with shell: false to correctly + * 1. ms-python.python extension API (handles conda, micromamba, venv, poetry, uv, pyenv, etc.) + * 2. PATH-based fallbacks (schemax, python3, python) for users without the Python extension + * + * The Python extension API path is spawned with shell: false to correctly * handle paths containing spaces (common on Windows/macOS). */ -function getCommandCandidates(): CommandCandidate[] { - const configured = vscode.workspace - .getConfiguration("python") - .get("defaultInterpreterPath"); - if (configured && configured.trim()) { - const pyPath = configured.trim(); +async function getCommandCandidates(): Promise { + const apiPath = await getPythonExtensionInterpreterPath(); + if (apiPath) { return [ - { cmd: pyPath, baseArgs: ["-m", "schemax.cli"], useShell: false }, + { cmd: apiPath, baseArgs: ["-m", "schemax.cli"], useShell: false }, ...BASE_COMMAND_CANDIDATES, ]; } @@ -99,6 +135,12 @@ function isCommandEnvelope(value: unknown): value is CommandEnvelope { } export class PythonBackendClient { + private log: (message: string) => void; + + constructor(log?: (message: string) => void) { + this.log = log ?? (() => {}); + } + async run(args: string[], cwd: string, options: RunOptions = {}): Promise { let lastFailure: PythonCommandResult = { success: false, @@ -108,9 +150,13 @@ export class PythonBackendClient { exitCode: null, }; - for (const candidate of getCommandCandidates()) { + const candidates = await getCommandCandidates(); + this.log(`[SchemaX] Python interpreter candidates: ${candidates.map((c) => c.cmd).join(", ")}`); + + for (const candidate of candidates) { const fullArgs = [...candidate.baseArgs, ...args]; const rendered = `${candidate.cmd} ${fullArgs.join(" ")}`; + this.log(`[SchemaX] Trying: ${rendered}`); const result = await this.runSingle( candidate.cmd, fullArgs, @@ -123,8 +169,10 @@ export class PythonBackendClient { return result; } if (result.success) { + this.log(`[SchemaX] Command succeeded: ${rendered}`); return result; } + this.log(`[SchemaX] Command failed (exit ${result.exitCode}): ${result.stderr?.split("\n")[0] ?? ""}`); lastFailure = result; } return lastFailure; diff --git a/packages/vscode-extension/src/contracts/workspace.ts b/packages/vscode-extension/src/contracts/workspace.ts index 6819511..56dd1f5 100644 --- a/packages/vscode-extension/src/contracts/workspace.ts +++ b/packages/vscode-extension/src/contracts/workspace.ts @@ -9,6 +9,7 @@ export interface Operation { op: string; target: string; payload: Record; + scope?: string | null; // v5 multi-target: which target scope this op belongs to } export interface ProviderCapabilities { diff --git a/packages/vscode-extension/src/extension.ts b/packages/vscode-extension/src/extension.ts index b38897d..b4012a7 100644 --- a/packages/vscode-extension/src/extension.ts +++ b/packages/vscode-extension/src/extension.ts @@ -12,10 +12,10 @@ import { validateRuntimeInfo } from "./backend/runtimeCompatibility"; let outputChannel: vscode.OutputChannel; let currentPanel: vscode.WebviewPanel | undefined; -const pythonBackend = new PythonBackendClient(); +let pythonBackend: PythonBackendClient; let extensionContextRef: vscode.ExtensionContext | undefined; const REQUIRED_ENVELOPE_SCHEMA_VERSION = "1"; -const MIN_SUPPORTED_CLI_VERSION = "0.2.10"; +const MIN_SUPPORTED_CLI_VERSION = "0.2.11"; interface BackendCompatibilityState { checked: boolean; @@ -144,6 +144,7 @@ function parseUndoRequestPayload(payload: unknown): UndoRequestPayload | null { export function activate(context: vscode.ExtensionContext) { extensionContextRef = context; outputChannel = vscode.window.createOutputChannel("SchemaX"); + pythonBackend = new PythonBackendClient((msg) => outputChannel.appendLine(msg)); outputChannel.appendLine("[SchemaX] Extension activating..."); outputChannel.appendLine("[SchemaX] Extension Activated!"); outputChannel.appendLine(`[SchemaX] Extension path: ${context.extensionPath}`); @@ -559,7 +560,8 @@ function parseImportProgressLine(line: string): ImportProgressUpdate | null { async function promptImportRequest(workspaceUri: vscode.Uri): Promise { const project = await storageV4.readProject(workspaceUri); - const envNames = Object.keys(project.provider.environments || {}); + const defaultTarget = storageV4.getTargetConfig(project); + const envNames = Object.keys(defaultTarget.environments || {}); if (envNames.length === 0) { vscode.window.showErrorMessage("SchemaX: No environments configured in project.json"); return null; @@ -1121,15 +1123,18 @@ async function promptForProjectSetup( try { await storageV4.ensureSchemaxDir(workspaceUri); - // Create v4 project - const newProject: storageV4.ProjectFileV4 = { - version: 4, + // Create v5 project with multi-target support + const newProject: storageV4.ProjectFileV5 = { + version: 5, name: projectName, - provider: { - type: selectedProvider.id, - version: selectedProvider.version, - environments: environments, + targets: { + default: { + type: selectedProvider.id, + version: selectedProvider.version, + environments: environments, + }, }, + defaultTarget: "default", snapshots: [], deployments: [], settings: { @@ -1344,6 +1349,7 @@ async function openDesigner(context: vscode.ExtensionContext) { } } + const defaultTarget = storageV4.getTargetConfig(project); const payloadForWebview = { ...project, state, @@ -1352,7 +1358,7 @@ async function openDesigner(context: vscode.ExtensionContext) { staleSnapshots: staleSnapshots.length > 0 ? staleSnapshots : null, validationResult, provider: { - ...project.provider, + ...defaultTarget, id: provider.id, name: provider.name, version: provider.version, @@ -1491,13 +1497,14 @@ async function openDesigner(context: vscode.ExtensionContext) { } // Send combined data to webview including provider info + const defaultTargetCfg = storageV4.getTargetConfig(project); const payloadForWebview = { ...project, state, ops: changelog.ops, conflicts, // Include conflict info if present provider: { - ...project.provider, // Keep environments and other project provider config + ...defaultTargetCfg, // Keep environments and other target config id: provider.id, name: provider.name, version: provider.version, @@ -1671,12 +1678,13 @@ async function openDesigner(context: vscode.ExtensionContext) { outputChannel.appendLine(`[SchemaX] - Snapshots: ${project.snapshots.length}`); // Send updated data to webview including provider info + const defaultTargetForPayload = storageV4.getTargetConfig(project); const payloadForWebview = { ...project, state, ops: changelog.ops, provider: { - ...project.provider, // Keep environments and other project provider config + ...defaultTargetForPayload, // Keep environments and other target config id: provider.id, name: provider.name, version: provider.version, @@ -1794,12 +1802,13 @@ async function openDesigner(context: vscode.ExtensionContext) { outputChannel.appendLine(`[SchemaX] Project configuration updated successfully`); // Send updated data to webview + const updatedDefaultTarget = storageV4.getTargetConfig(project); const payloadForWebview = { ...project, state, ops: changelog.ops, provider: { - ...project.provider, + ...updatedDefaultTarget, id: provider.id, name: provider.name, version: provider.version, @@ -1895,7 +1904,8 @@ async function showLastOps() { outputChannel.clear(); outputChannel.appendLine("SchemaX: Last 20 Emitted Changes"); outputChannel.appendLine("=".repeat(80)); - outputChannel.appendLine(`Provider: ${project.provider.type}`); + const defaultTargetForOps = storageV4.getTargetConfig(project); + outputChannel.appendLine(`Provider: ${defaultTargetForOps.type}`); outputChannel.appendLine(""); if (lastOps.length === 0) { @@ -1910,7 +1920,7 @@ async function showLastOps() { } outputChannel.show(); - trackEvent("last_ops_shown", { count: lastOps.length, provider: project.provider.type }); + trackEvent("last_ops_shown", { count: lastOps.length, provider: defaultTargetForOps.type }); } catch (error) { vscode.window.showErrorMessage(`Failed to read operations: ${error}`); } @@ -1978,7 +1988,8 @@ async function createSnapshotCommand_impl() { const changelog = await storageV4.readChangelog(workspaceFolder.uri); const uncommittedOpsCount = changelog.ops.length; - outputChannel.appendLine(`[SchemaX] Provider: ${project.provider.type}`); + const snapshotTarget = storageV4.getTargetConfig(project); + outputChannel.appendLine(`[SchemaX] Provider: ${snapshotTarget.type}`); outputChannel.appendLine(`[SchemaX] Uncommitted operations: ${uncommittedOpsCount}`); outputChannel.appendLine(`[SchemaX] Existing snapshots: ${project.snapshots.length}`); @@ -2102,12 +2113,13 @@ async function createSnapshotCommand_impl() { workspaceFolder.uri, false ); + const snapshotDefaultTarget = storageV4.getTargetConfig(updatedProject); const payloadForWebview = { ...updatedProject, state, ops: changelog.ops, provider: { - ...updatedProject.provider, // Keep environments and other project provider config + ...snapshotDefaultTarget, // Keep environments and other target config id: provider.id, name: provider.name, version: provider.version, @@ -2130,7 +2142,7 @@ async function createSnapshotCommand_impl() { trackEvent("snapshot_created", { version: snapshot.version, opsCount: uncommittedOpsCount, - provider: updatedProject.provider.type, + provider: storageV4.getTargetConfig(updatedProject).type, }); } ); @@ -2170,7 +2182,8 @@ async function generateSQLMigration() { const project = await storageV4.readProject(workspaceFolder.uri); // Ask for target environment - const environments = Object.keys(project.provider.environments); + const sqlTarget = storageV4.getTargetConfig(project); + const environments = Object.keys(sqlTarget.environments); if (environments.length === 0) { vscode.window.showErrorMessage("No environments configured in project.json"); return; @@ -2225,7 +2238,7 @@ async function generateSQLMigration() { trackEvent("sql_generated", { sqlLength: fileContent.length, - provider: project.provider.type, + provider: sqlTarget.type, }); } catch (error) { outputChannel.appendLine(`[SchemaX] ERROR: SQL generation failed: ${error}`); diff --git a/packages/vscode-extension/src/storage-v4.ts b/packages/vscode-extension/src/storage-v4.ts index fa0b7bc..487dfbc 100644 --- a/packages/vscode-extension/src/storage-v4.ts +++ b/packages/vscode-extension/src/storage-v4.ts @@ -1,8 +1,8 @@ /** - * Storage Layer V4 - Multi-Environment Support + * Storage Layer V5 - Multi-Target / Multi-Provider Support * - * Supports environment-specific catalog configurations with logical → physical name mapping. - * Breaking change from v3: environments are now rich objects instead of simple arrays. + * Supports multiple named targets (provider instances) per project. + * Transparent auto-migration from v4 → v5 on first load. */ import * as vscode from "vscode"; @@ -102,13 +102,20 @@ export interface EnvironmentConfig { existingObjects?: ExistingObjectsConfig; } -// Project File V4 Types +// Project File V4 Types (legacy, auto-migrated to v5) interface ProviderConfigV4 { type: string; // 'unity', 'hive', 'postgres' version: string; // Provider schema version environments: Record; } +// Target config in v5 = same shape as the old ProviderConfigV4 +export interface TargetConfig { + type: string; + version: string; + environments: Record; +} + interface ProjectSettings { autoIncrementVersion: boolean; versionPrefix: string; @@ -143,6 +150,23 @@ interface Deployment { driftDetails?: unknown[]; } +// V5 project file with multi-target support +export interface ProjectFileV5 { + version: 5; + name: string; + targets: Record; + defaultTarget: string; + + managedLocations?: Record; + externalLocations?: Record; + + snapshots: SnapshotMetadata[]; + deployments: Deployment[]; + settings: ProjectSettings; + latestSnapshot: string | null; +} + +// Keep V4 type for migration purposes export interface ProjectFileV4 { version: 4; name: string; @@ -160,6 +184,40 @@ export interface ProjectFileV4 { latestSnapshot: string | null; } +// Union type: a loaded project is always v5 after migration +export type ProjectFile = ProjectFileV5; + +/** + * Migrate a v4 project to v5 in-memory by wrapping provider into targets. + */ +function migrateV4ToV5(v4: ProjectFileV4): ProjectFileV5 { + const { provider, version: _version, ...rest } = v4; + return { + ...rest, + version: 5, + targets: { default: provider }, + defaultTarget: "default", + }; +} + +/** + * Get the target config for a given target name, falling back to defaultTarget. + */ +export function getTargetConfig( + project: ProjectFileV5, + scope?: string | null +): TargetConfig { + const resolved = scope || project.defaultTarget || "default"; + const config = project.targets[resolved]; + if (!config) { + const available = Object.keys(project.targets).join(", "); + throw new Error( + `Target '${resolved}' not found in project. Available targets: ${available}` + ); + } + return config; +} + export interface ChangelogFile { version: 1; sinceSnapshot: string | null; @@ -292,16 +350,23 @@ export async function ensureProjectFile( // File exists, check version const content = await vscode.workspace.fs.readFile(projectPath); - const project = JSON.parse(content.toString()) as ProjectFileV4; + const raw = JSON.parse(content.toString()) as { version: number }; - if (project.version === 4) { - outputChannel.appendLine("[SchemaX] Project file already exists (v4)"); + if (raw.version === 5) { + outputChannel.appendLine("[SchemaX] Project file already exists (v5)"); + return; + } else if (raw.version === 4) { + // Auto-migrate v4 → v5 + const v4 = raw as unknown as ProjectFileV4; + const v5 = migrateV4ToV5(v4); + const migrated = Buffer.from(JSON.stringify(v5, null, 2), "utf8"); + await vscode.workspace.fs.writeFile(projectPath, migrated); + outputChannel.appendLine("[SchemaX] Auto-migrated project v4 → v5"); return; } else { throw new Error( - `Project version ${project.version} not supported. ` + - "This version of SchemaX requires v4 projects. " + - "Please create a new project or manually migrate to v4." + `Project version ${raw.version} not supported. ` + + "This version of SchemaX requires v4 or v5 projects." ); } } catch (error: unknown) { @@ -310,47 +375,49 @@ export async function ensureProjectFile( } } - // Create new v4 project + // Create new v5 project const workspaceName = path.basename(workspaceUri.fsPath); - // Create v4 project with environment configuration - const newProject: ProjectFileV4 = { - version: 4, + const newProject: ProjectFileV5 = { + version: 5, name: workspaceName, - provider: { - type: providerId, - version: DEFAULT_PROVIDER_VERSION, - environments: { - dev: { - topLevelName: `dev_${workspaceName}`, - catalogMappings: {}, - description: "Development environment", - allowDrift: true, - requireSnapshot: false, - autoCreateTopLevel: true, - autoCreateSchemaxSchema: true, - }, - test: { - topLevelName: `test_${workspaceName}`, - catalogMappings: {}, - description: "Test/staging environment", - allowDrift: false, - requireSnapshot: true, - autoCreateTopLevel: true, - autoCreateSchemaxSchema: true, - }, - prod: { - topLevelName: `prod_${workspaceName}`, - catalogMappings: {}, - description: "Production environment", - allowDrift: false, - requireSnapshot: true, - requireApproval: false, - autoCreateTopLevel: false, - autoCreateSchemaxSchema: true, + targets: { + default: { + type: providerId, + version: DEFAULT_PROVIDER_VERSION, + environments: { + dev: { + topLevelName: `dev_${workspaceName}`, + catalogMappings: {}, + description: "Development environment", + allowDrift: true, + requireSnapshot: false, + autoCreateTopLevel: true, + autoCreateSchemaxSchema: true, + }, + test: { + topLevelName: `test_${workspaceName}`, + catalogMappings: {}, + description: "Test/staging environment", + allowDrift: false, + requireSnapshot: true, + autoCreateTopLevel: true, + autoCreateSchemaxSchema: true, + }, + prod: { + topLevelName: `prod_${workspaceName}`, + catalogMappings: {}, + description: "Production environment", + allowDrift: false, + requireSnapshot: true, + requireApproval: false, + autoCreateTopLevel: false, + autoCreateSchemaxSchema: true, + }, }, }, }, + defaultTarget: "default", snapshots: [], deployments: [], settings: { @@ -378,31 +445,39 @@ export async function ensureProjectFile( const changelogContent = Buffer.from(JSON.stringify(newChangelog, null, 2), "utf8"); await vscode.workspace.fs.writeFile(changelogPath, changelogContent); - outputChannel.appendLine(`[SchemaX] Initialized new v4 project: ${workspaceName}`); + outputChannel.appendLine(`[SchemaX] Initialized new v5 project: ${workspaceName}`); outputChannel.appendLine(`[SchemaX] Provider: ${getProviderDisplayName(providerId)}`); outputChannel.appendLine("[SchemaX] Environments: dev, test, prod"); } /** - * Read project file (v4 only) + * Read project file (v4 auto-migrated to v5, or v5 native) */ -export async function readProject(workspaceUri: vscode.Uri): Promise { +export async function readProject(workspaceUri: vscode.Uri): Promise { const projectPath = getProjectFilePath(workspaceUri); try { const content = await vscode.workspace.fs.readFile(projectPath); - const project = JSON.parse(content.toString()) as ProjectFileV4; + const raw = JSON.parse(content.toString()) as { version: number }; - // Enforce v4 - if (project.version !== 4) { - throw new Error( - `Project version ${project.version} not supported. ` + - "This version of SchemaX requires v4 projects. " + - "Please create a new project or manually migrate to v4." - ); + if (raw.version === 5) { + return raw as unknown as ProjectFileV5; } - return project; + if (raw.version === 4) { + // Transparent v4 → v5 migration + const v4 = raw as unknown as ProjectFileV4; + const v5 = migrateV4ToV5(v4); + // Persist the migrated file + const migrated = Buffer.from(JSON.stringify(v5, null, 2), "utf8"); + await vscode.workspace.fs.writeFile(projectPath, migrated); + return v5; + } + + throw new Error( + `Project version ${raw.version} not supported. ` + + "This version of SchemaX requires v4 or v5 projects." + ); } catch (error: unknown) { if (isFileNotFoundError(error)) { throw new Error("Project file not found. Please initialize a new project first."); @@ -416,7 +491,7 @@ export async function readProject(workspaceUri: vscode.Uri): Promise { const projectPath = getProjectFilePath(workspaceUri); await writeJsonFileCoalesced(projectPath, project); @@ -640,21 +715,24 @@ function sanitizeCatalogNameForPhysical(name: string): string { * This adds default mappings (envName_sanitizedCatalogName) for any new catalog so apply works. */ export function ensureCatalogMappingsForNewCatalogs( - project: ProjectFileV4, - ops: Operation[] -): { project: ProjectFileV4; updated: boolean } { + project: ProjectFileV5, + ops: Operation[], + scope?: string | null +): { project: ProjectFileV5; updated: boolean } { const addCatalogOps = ops.filter( (op) => op.op?.endsWith("add_catalog") && (op.payload as { name?: string })?.name ); const newNames = [...new Set(addCatalogOps.map((op) => (op.payload as { name: string }).name))]; - if (newNames.length === 0 || !project.provider?.environments) { + if (newNames.length === 0) { return { project, updated: false }; } + const resolvedTarget = scope || project.defaultTarget || "default"; + const targetConfig = getTargetConfig(project, scope); let updated = false; const environments: Record = {}; - for (const [envName, config] of Object.entries(project.provider.environments)) { + for (const [envName, config] of Object.entries(targetConfig.environments)) { const catalogMappings = { ...(config.catalogMappings || {}) }; for (const logicalName of newNames) { if (catalogMappings[logicalName] !== undefined) continue; @@ -670,9 +748,12 @@ export function ensureCatalogMappingsForNewCatalogs( return { project: { ...project, - provider: { - ...project.provider, - environments, + targets: { + ...project.targets, + [resolvedTarget]: { + ...targetConfig, + environments, + }, }, }, updated: true, @@ -714,7 +795,7 @@ export async function createSnapshot( version?: string, comment?: string, tags: string[] = [] -): Promise<{ project: ProjectFileV4; snapshot: SnapshotFile }> { +): Promise<{ project: ProjectFileV5; snapshot: SnapshotFile }> { const project = await readProject(workspaceUri); const changelog = await readChangelog(workspaceUri); @@ -797,16 +878,18 @@ export async function getUncommittedOpsCount(workspaceUri: vscode.Uri): Promise< } /** - * Get environment configuration + * Get environment configuration from a target */ export function getEnvironmentConfig( - project: ProjectFileV4, - environment: string + project: ProjectFileV5, + environment: string, + scope?: string | null ): EnvironmentConfig { - const envConfig = project.provider.environments[environment]; + const target = getTargetConfig(project, scope); + const envConfig = target.environments[environment]; if (!envConfig) { - const available = Object.keys(project.provider.environments).join(", "); + const available = Object.keys(target.environments).join(", "); throw new Error( `Environment '${environment}' not found in project. ` + `Available environments: ${available}` ); diff --git a/packages/vscode-extension/src/webview/components/EnvironmentSummary.tsx b/packages/vscode-extension/src/webview/components/EnvironmentSummary.tsx index a5858c8..77539bf 100644 --- a/packages/vscode-extension/src/webview/components/EnvironmentSummary.tsx +++ b/packages/vscode-extension/src/webview/components/EnvironmentSummary.tsx @@ -1,6 +1,7 @@ import React from "react"; import { VSCodeButton } from "@vscode/webview-ui-toolkit/react"; import { useDesignerStore } from "../state/useDesignerStore"; +import { getDefaultTargetConfig } from "../models/unity"; import { getVsCodeApi } from "../vscode-api"; interface EnvironmentConfig { @@ -29,7 +30,12 @@ export const EnvironmentSummary: React.FC = ({ classNam } }, []); - const environments = project?.provider?.environments; + let environments: Record | undefined; + try { + environments = project ? getDefaultTargetConfig(project).environments : undefined; + } catch { + environments = undefined; + } if (!environments || Object.keys(environments).length === 0) { return null; } diff --git a/packages/vscode-extension/src/webview/components/ImportAssetsPanel.tsx b/packages/vscode-extension/src/webview/components/ImportAssetsPanel.tsx index 93eb57b..453056a 100644 --- a/packages/vscode-extension/src/webview/components/ImportAssetsPanel.tsx +++ b/packages/vscode-extension/src/webview/components/ImportAssetsPanel.tsx @@ -7,6 +7,7 @@ import { } from "@vscode/webview-ui-toolkit/react"; import { getVsCodeApi } from "../vscode-api"; import type { ProjectFile } from "../models/unity"; +import { getDefaultTargetConfig } from "../models/unity"; const vscode = getVsCodeApi(); @@ -115,7 +116,7 @@ export function ImportAssetsPanel({ onCancel, pickedSqlFilePath = null, }: ImportAssetsPanelProps) { - const envNames = Object.keys(project.provider?.environments || {}); + const envNames = Object.keys(getDefaultTargetConfig(project)?.environments || {}); const [activeTab, setActiveTab] = React.useState("databricks"); const [target, setTarget] = React.useState(envNames[0] || "dev"); const [profile, setProfile] = React.useState("DEFAULT"); @@ -593,7 +594,8 @@ function parseCatalogMappings(input: string): Record | undefined } function buildSuggestedCatalogMappingsText(project: ProjectFile, target: string): string { - const envCfg = (project.provider?.environments?.[target] || {}) as { + const targetConfig = getDefaultTargetConfig(project); + const envCfg = (targetConfig?.environments?.[target] || {}) as { catalogMappings?: Record; topLevelName?: string; }; diff --git a/packages/vscode-extension/src/webview/components/ProjectSettingsPanel.tsx b/packages/vscode-extension/src/webview/components/ProjectSettingsPanel.tsx index 8b1bf44..f577dc9 100644 --- a/packages/vscode-extension/src/webview/components/ProjectSettingsPanel.tsx +++ b/packages/vscode-extension/src/webview/components/ProjectSettingsPanel.tsx @@ -1,7 +1,8 @@ -import React, { useState, useEffect } from "react"; +import React, { useState } from "react"; import { VSCodeButton, VSCodeTextField } from "@vscode/webview-ui-toolkit/react"; -import type { ProjectFile } from "../models/unity"; +import type { ProjectFile, TargetConfig } from "../models/unity"; import { getVsCodeApi } from "../vscode-api"; +import { UnityTargetSettings } from "./settings/UnityTargetSettings"; const vscode = getVsCodeApi(); @@ -12,31 +13,7 @@ interface ProjectSettingsPanelProps { interface LocationDefinition { description?: string; - paths: Record; // environmentName -> physical path -} - -const ALL_MANAGED_CATEGORIES = [ - { id: "catalog_structure", label: "Catalog structure" }, - { id: "schema_structure", label: "Schema structure" }, - { id: "table_structure", label: "Table structure" }, - { id: "view_structure", label: "View structure" }, - { id: "volume_structure", label: "Volume structure" }, - { id: "function_structure", label: "Function structure" }, - { id: "materialized_view_structure", label: "Materialized view structure" }, - { id: "governance", label: "Governance" }, -] as const; - -interface EnvironmentConfig { - topLevelName: string; - catalogMappings?: Record; - description?: string; - allowDrift: boolean; - requireSnapshot: boolean; - requireApproval?: boolean; - autoCreateTopLevel: boolean; - autoCreateSchemaxSchema: boolean; - managedCategories?: string[]; - existingObjects?: { catalog?: string[]; schema?: string[]; table?: string[] }; + paths: Record; } interface LocationModalData { @@ -44,90 +21,39 @@ interface LocationModalData { mode: "add" | "edit"; name: string; description: string; - paths: Record; // env -> path + paths: Record; } type LocationMap = Record; -type EnvironmentMap = Record; - -function formatCatalogMappingsText(mappings?: Record): string { - if (!mappings) { - return ""; - } - return Object.entries(mappings) - .sort(([left], [right]) => left.localeCompare(right)) - .map(([logical, physical]) => `${logical}=${physical}`) - .join("\n"); -} - -function parseCatalogMappingsText(text: string): Record { - const mappings: Record = {}; - const lines = text - .split("\n") - .map((line) => line.trim()) - .filter(Boolean); - - for (const line of lines) { - const separator = line.indexOf("="); - if (separator <= 0 || separator === line.length - 1) { - throw new Error(`Invalid mapping '${line}'. Expected format logical=physical`); - } - const logical = line.slice(0, separator).trim(); - const physical = line.slice(separator + 1).trim(); - if (!logical || !physical) { - throw new Error(`Invalid mapping '${line}'. Expected format logical=physical`); - } - mappings[logical] = physical; - } - - return mappings; -} export function ProjectSettingsPanel({ project, onClose }: ProjectSettingsPanelProps) { const [editedProject, setEditedProject] = useState( JSON.parse(JSON.stringify(project)) ); - const [expandedEnvs, setExpandedEnvs] = useState>( - new Set([project.activeEnvironment || "dev"]) + const [isDirty, setIsDirty] = useState(false); + const [hasMappingErrors, setHasMappingErrors] = useState(false); + + // Target tab state + const targetNames = Object.keys(editedProject.targets || {}); + const [activeTargetTab, setActiveTargetTab] = useState( + editedProject.defaultTarget || targetNames[0] || "default" ); - const [editingCatalog, setEditingCatalog] = useState(null); - const [editCatalogValue, setEditCatalogValue] = useState(""); + + // Location modal state const [showLocationModal, setShowLocationModal] = useState(false); const [locationModalData, setLocationModalData] = useState(null); + const [locationNameError, setLocationNameError] = useState(null); const [deleteConfirmation, setDeleteConfirmation] = useState<{ type: "managed" | "external"; name: string; } | null>(null); - const [isDirty, setIsDirty] = useState(false); - const [locationNameError, setLocationNameError] = useState(null); - const [mappingTextByEnv, setMappingTextByEnv] = useState>({}); - const [mappingErrorByEnv, setMappingErrorByEnv] = useState>({}); + const activeTargetConfig = editedProject.targets?.[activeTargetTab]; const activeEnv = project.activeEnvironment || "dev"; - const environments: EnvironmentMap = - (editedProject.provider?.environments as EnvironmentMap) || {}; - const environmentNames = Object.keys(environments); const logicalCatalogs = editedProject.state?.catalogs || []; - const hasMappingErrors = Object.values(mappingErrorByEnv).some(Boolean); - useEffect(() => { - const initialText: Record = {}; - environmentNames.forEach((envName) => { - initialText[envName] = formatCatalogMappingsText(environments[envName]?.catalogMappings); - }); - setMappingTextByEnv(initialText); - setMappingErrorByEnv({}); - }, [editedProject.provider?.environments]); - - const toggleEnv = (envName: string) => { - const newExpanded = new Set(expandedEnvs); - if (newExpanded.has(envName)) { - newExpanded.delete(envName); - } else { - newExpanded.add(envName); - } - setExpandedEnvs(newExpanded); - }; + // Derive environment names from active target for location modal + const environmentNames = Object.keys(activeTargetConfig?.environments || {}); const handleSave = () => { vscode.postMessage({ @@ -148,145 +74,49 @@ export function ProjectSettingsPanel({ project, onClose }: ProjectSettingsPanelP } }; - const startEditCatalog = (envName: string) => { - setEditingCatalog(envName); - setEditCatalogValue(environments[envName]?.topLevelName || ""); - }; - - const saveCatalogEdit = (envName: string) => { - const updated = { ...editedProject }; - if (updated.provider?.environments?.[envName]) { - updated.provider.environments[envName].topLevelName = editCatalogValue; - setEditedProject(updated); - setIsDirty(true); - } - setEditingCatalog(null); - }; - - const updateCatalogMappings = (envName: string, rawText: string) => { - setMappingTextByEnv((current) => ({ ...current, [envName]: rawText })); - - try { - const mappings = parseCatalogMappingsText(rawText); - const updated = { ...editedProject }; - if (updated.provider?.environments?.[envName]) { - updated.provider.environments[envName].catalogMappings = mappings; - setEditedProject(updated); - setIsDirty(true); - } - setMappingErrorByEnv((current) => ({ ...current, [envName]: "" })); - } catch (error) { - setMappingErrorByEnv((current) => ({ ...current, [envName]: String(error) })); - } - }; - - const cancelCatalogEdit = () => { - setEditingCatalog(null); - setEditCatalogValue(""); - }; - - const toggleManagedCategory = (envName: string, categoryId: string) => { - const updated = { ...editedProject }; - const providerEnvironments = updated.provider?.environments; - if (!providerEnvironments) return; - const env = providerEnvironments[envName]; - if (!env) return; - const current = env.managedCategories ?? ALL_MANAGED_CATEGORIES.map((c) => c.id); - const set = new Set(current); - if (set.has(categoryId)) { - set.delete(categoryId); - } else { - set.add(categoryId); - } - const next = [...set]; - providerEnvironments[envName] = { - ...env, - managedCategories: next.length === ALL_MANAGED_CATEGORIES.length ? undefined : next, - }; - setEditedProject(updated); + const handleTargetConfigChange = (targetName: string, updated: TargetConfig) => { + const next = { ...editedProject }; + next.targets = { ...next.targets, [targetName]: updated }; + setEditedProject(next); setIsDirty(true); }; - const updateExistingCatalogs = (envName: string, rawText: string) => { - const updated = { ...editedProject }; - const providerEnvironments = updated.provider?.environments; - if (!providerEnvironments) return; - const env = providerEnvironments[envName]; - if (!env) return; - const list = rawText - .split(/[\n,]/) - .map((s) => s.trim()) - .filter(Boolean); - providerEnvironments[envName] = { - ...env, - existingObjects: { - ...(env.existingObjects ?? {}), - catalog: list.length > 0 ? list : undefined, - }, - }; - setEditedProject(updated); - setIsDirty(true); - }; + // ── Location helpers ────────────────────────────────────────────── - // Add Location const openAddLocationModal = (type: "managed" | "external") => { const initialPaths: Record = {}; environmentNames.forEach((env) => { initialPaths[env] = ""; }); - - setLocationModalData({ - type, - mode: "add", - name: "", - description: "", - paths: initialPaths, - }); + setLocationModalData({ type, mode: "add", name: "", description: "", paths: initialPaths }); setLocationNameError(null); setShowLocationModal(true); }; - // Edit Location const openEditLocationModal = (type: "managed" | "external", name: string) => { const locations = type === "managed" ? editedProject.managedLocations || {} : editedProject.externalLocations || {}; - const location = locations[name]; if (!location) return; - // Ensure all environments have a path entry (even if empty) const paths: Record = { ...location.paths }; environmentNames.forEach((env) => { - if (!(env in paths)) { - paths[env] = ""; - } - }); - - setLocationModalData({ - type, - mode: "edit", - name, - description: location.description || "", - paths, + if (!(env in paths)) paths[env] = ""; }); + setLocationModalData({ type, mode: "edit", name, description: location.description || "", paths }); setShowLocationModal(true); }; - // Save Location const saveLocation = () => { if (!locationModalData) return; - const { type, mode, name, description, paths } = locationModalData; - // Validation if (!name.trim()) { setLocationNameError("Location name is required"); return; } - - // Unity Catalog identifiers: letters, numbers, underscores only (no hyphens) if (!/^[a-z][a-z0-9_]*$/.test(name)) { setLocationNameError( "Use only lowercase letters, numbers, and underscores (e.g. my_location). Hyphens are not allowed." @@ -295,32 +125,24 @@ export function ProjectSettingsPanel({ project, onClose }: ProjectSettingsPanelP } setLocationNameError(null); - // Check for at least one path const hasPath = Object.values(paths).some((p) => p.trim()); if (!hasPath) { alert("At least one environment path must be configured"); return; } - // Check for duplicate name (only in add mode) if (mode === "add") { - const existingLocations = - type === "managed" - ? editedProject.managedLocations || {} - : editedProject.externalLocations || {}; - - if (name in existingLocations) { + const existing = + type === "managed" ? editedProject.managedLocations || {} : editedProject.externalLocations || {}; + if (name in existing) { alert(`A ${type} location with name "${name}" already exists`); return; } } - // Clean up paths (remove empty ones) const cleanedPaths: Record = {}; Object.entries(paths).forEach(([env, path]) => { - if (path.trim()) { - cleanedPaths[env] = path.trim(); - } + if (path.trim()) cleanedPaths[env] = path.trim(); }); const newLocation: LocationDefinition = { @@ -329,7 +151,6 @@ export function ProjectSettingsPanel({ project, onClose }: ProjectSettingsPanelP }; const updated = { ...editedProject }; - if (type === "managed") { if (!updated.managedLocations) updated.managedLocations = {}; updated.managedLocations[name] = newLocation; @@ -345,28 +166,52 @@ export function ProjectSettingsPanel({ project, onClose }: ProjectSettingsPanelP setLocationNameError(null); }; - // Delete Location const confirmDeleteLocation = (type: "managed" | "external", name: string) => { setDeleteConfirmation({ type, name }); }; const deleteLocation = () => { if (!deleteConfirmation) return; - const { type, name } = deleteConfirmation; const updated = { ...editedProject }; - if (type === "managed" && updated.managedLocations) { delete updated.managedLocations[name]; } else if (type === "external" && updated.externalLocations) { delete updated.externalLocations[name]; } - setEditedProject(updated); setIsDirty(true); setDeleteConfirmation(null); }; + // ── Provider-specific panel renderer ────────────────────────────── + + function renderTargetSettings(tName: string, tConfig: TargetConfig) { + switch (tConfig.type) { + case "unity": + return ( + handleTargetConfigChange(tName, updated)} + onMappingErrorChange={setHasMappingErrors} + /> + ); + default: + return ( +
+

Provider: {tConfig.type}

+

+ Settings for provider {tConfig.type} are not yet available in the UI. +

+
+ ); + } + } + + // ── Render ───────────────────────────────────────────────────────── + return (
e.stopPropagation()}> @@ -386,12 +231,6 @@ export function ProjectSettingsPanel({ project, onClose }: ProjectSettingsPanelP Project name {editedProject.name}
-
- Provider - - {editedProject.provider?.type} · v{editedProject.provider?.version} - -
Latest snapshot {editedProject.latestSnapshot || "None yet"} @@ -400,9 +239,37 @@ export function ProjectSettingsPanel({ project, onClose }: ProjectSettingsPanelP Snapshots {editedProject.snapshots?.length || 0}
+
+ Targets + {targetNames.length} +
+ {/* Target Tabs */} + {targetNames.length > 0 && ( + <> +
+ {targetNames.map((tName) => { + const tCfg = editedProject.targets[tName]; + const isActive = tName === activeTargetTab; + return ( + + ); + })} +
+ + {activeTargetConfig && renderTargetSettings(activeTargetTab, activeTargetConfig)} + + )} + {/* Project-level Managed Locations */}

Physical Isolation (Managed Tables)

@@ -450,7 +317,7 @@ export function ProjectSettingsPanel({ project, onClose }: ProjectSettingsPanelP
) : (
- No managed locations configured. Catalogs and schemas will use Unity Catalog's + No managed locations configured. Catalogs and schemas will use the provider's default storage.
)} @@ -513,154 +380,6 @@ export function ProjectSettingsPanel({ project, onClose }: ProjectSettingsPanelP + Add External Location - - {/* Environment Configuration */} -
-

Environment Configuration

-

- Configure catalog mappings (Logical Isolation) for each environment. -

- - {Object.entries(environments).map(([envName, envConfig]) => ( -
-
toggleEnv(envName)}> - {expandedEnvs.has(envName) ? "▼" : "▶"} - {envName} - {envName === activeEnv && ● Active} -
- - {expandedEnvs.has(envName) && ( -
- {/* Logical Isolation (Catalog Mapping) */} -
-

Logical Isolation

-

- Configure logical-to-physical catalog mapping for this environment. -

- - {editingCatalog === envName ? ( -
- - setEditCatalogValue((e.target as HTMLInputElement).value) - } - placeholder="physical_catalog_name" - /> - saveCatalogEdit(envName)}>Save - - Cancel - -
- ) : ( -
- Tracking Catalog: - {envConfig.topLevelName} - startEditCatalog(envName)}> - ✏️ - -
- )} -
- -