diff --git a/.gitignore b/.gitignore
index cea89fa25a1..233c6caf1e9 100644
--- a/.gitignore
+++ b/.gitignore
@@ -37,6 +37,7 @@ python_sdk/dist/*
!backend/tests/unit/**/data_export/*.csv
# Generated files
+/.generated/
query_performance_results/
sync/dist/
helm/charts/
diff --git a/backend/infrahub/computed_attribute/tasks.py b/backend/infrahub/computed_attribute/tasks.py
index ef56605da5f..08db6298f28 100644
--- a/backend/infrahub/computed_attribute/tasks.py
+++ b/backend/infrahub/computed_attribute/tasks.py
@@ -50,6 +50,10 @@ def _chunk_ids(ids: list[str], chunk_size: int) -> list[list[str]]:
return [ids[i : i + chunk_size] for i in range(0, len(ids), chunk_size)]
+def _get_submission_chunk_size() -> int:
+ return max(1, get_prefect_max_related_resources() // 2)
+
+
UPDATE_ATTRIBUTE = """
mutation UpdateAttribute(
$id: String!,
@@ -212,7 +216,7 @@ async def trigger_update_python_computed_attributes(
if not object_ids:
return
- chunk_size = get_prefect_max_related_resources() // 2
+ chunk_size = _get_submission_chunk_size()
for chunk in _chunk_ids(object_ids, chunk_size):
await get_workflow().submit_workflow(
workflow=COMPUTED_ATTRIBUTE_PROCESS_TRANSFORM,
@@ -554,7 +558,7 @@ async def query_transform_targets(
key = (subscriber.kind, computed_attribute.name)
batches.setdefault(key, []).append(subscriber.object_id)
- chunk_size = get_prefect_max_related_resources() // 2
+ chunk_size = _get_submission_chunk_size()
for (kind, attribute_name), batch_object_ids in batches.items():
for chunk in _chunk_ids(batch_object_ids, chunk_size):
await get_workflow().submit_workflow(
diff --git a/backend/infrahub/core/ipam/utilization.py b/backend/infrahub/core/ipam/utilization.py
index 8507f7b69c8..9e7fb604dc7 100644
--- a/backend/infrahub/core/ipam/utilization.py
+++ b/backend/infrahub/core/ipam/utilization.py
@@ -67,8 +67,9 @@ async def get_children(
if ip_prefixes is None:
ip_prefixes = self.ip_prefixes
result: list[PrefixChildDetails] = []
- for prefix in ip_prefixes:
- for child in self._results_by_prefix_id.get(prefix.get_id(), []):
+ prefix_ids = {prefix.get_id() for prefix in ip_prefixes}
+ for prefix_id in prefix_ids:
+ for child in self._results_by_prefix_id.get(prefix_id, []):
if prefix_member_type and child.child_type != prefix_member_type:
continue
result.append(child)
diff --git a/backend/infrahub/git/base.py b/backend/infrahub/git/base.py
index 32da2c63a50..2b0cf966f21 100644
--- a/backend/infrahub/git/base.py
+++ b/backend/infrahub/git/base.py
@@ -705,7 +705,8 @@ def create_commit_worktree(self, commit: str) -> bool | Worktree:
"""Create a new worktree for a given commit.
Raises:
- RepositoryError: When the worktree cannot be created and the commit cannot be fetched from a remote.
+ CommitNotFoundError: When the commit does not exist in the local clone.
+ RepositoryError: When the worktree cannot be created for any other reason.
"""
# Check of the worktree already exist
@@ -721,22 +722,9 @@ def create_commit_worktree(self, commit: str) -> bool | Worktree:
log.debug(f"Commit worktree created {commit}", repository=self.name)
return worktree
except GitCommandError as exc:
- if "invalid reference" not in exc.stderr:
- raise RepositoryError(identifier=self.name, message=exc.stderr) from exc
-
- if not self.has_origin:
- raise RepositoryError(
- identifier=self.name,
- message=f"Commit {commit} not found and no remote origin configured to fetch from.",
- ) from exc
-
- # Commit may exist on the remote but hasn't been fetched to this worker yet
- log.info(f"Commit {commit} not found locally, fetching from remote.", repository=self.name)
- repo.remotes.origin.fetch()
-
- repo.git.worktree("add", directory, commit)
- log.debug(f"Commit worktree created {commit} after fetch", repository=self.name)
- return worktree
+ if "invalid reference" in exc.stderr:
+ raise CommitNotFoundError(identifier=self.name, commit=commit) from exc
+ raise RepositoryError(identifier=self.name, message=exc.stderr) from exc
def create_branch_worktree(self, branch_name: str, branch_id: str) -> bool:
"""Create a new worktree for a given branch.
@@ -761,7 +749,13 @@ def create_branch_worktree(self, branch_name: str, branch_id: str) -> bool:
async def calculate_diff_between_commits(
self, first_commit: str, second_commit: str
) -> tuple[list[str], list[str], list[str]]:
- """TODO need to refactor this function to return more information.
+ """Return the (changed, added, removed) files going from first_commit to second_commit.
+
+ Direction follows `git diff first_commit second_commit`: first_commit is the old/base side
+ and second_commit is the new/target side. A file present only in second_commit is "added";
+ a file present only in first_commit is "removed".
+
+ TODO need to refactor this function to return more information.
Like :
- What has changed inside the files
@@ -777,12 +771,12 @@ async def calculate_diff_between_commits(
added_files = []
for x in commit_in_branch.diff(commit_to_compare, create_patch=True):
- if x.a_blob and not x.b_blob and x.a_blob.path not in added_files:
- added_files.append(x.a_blob.path)
+ if x.a_blob and not x.b_blob and x.a_blob.path not in removed_files:
+ removed_files.append(x.a_blob.path)
elif x.a_blob and x.b_blob and x.a_blob.path not in changed_files:
changed_files.append(x.a_blob.path)
- elif not x.a_blob and x.b_blob and x.b_blob.path not in removed_files:
- removed_files.append(x.b_blob.path)
+ elif not x.a_blob and x.b_blob and x.b_blob.path not in added_files:
+ added_files.append(x.b_blob.path)
return changed_files, added_files, removed_files
diff --git a/backend/infrahub/git/integrator.py b/backend/infrahub/git/integrator.py
index afa05c16474..67eb093ea3a 100644
--- a/backend/infrahub/git/integrator.py
+++ b/backend/infrahub/git/integrator.py
@@ -45,7 +45,7 @@
from pydantic import ValidationError as PydanticValidationError
from typing_extensions import Self
-from infrahub import config
+from infrahub import config, lock
from infrahub.core.constants import ArtifactStatus, ContentType, InfrahubKind, RepositoryObjects, RepositorySyncStatus
from infrahub.core.registry import registry
from infrahub.events.artifact_action import ArtifactCreatedEvent, ArtifactUpdatedEvent
@@ -53,6 +53,7 @@
from infrahub.events.repository_action import CommitUpdatedEvent
from infrahub.exceptions import (
CheckError,
+ CommitNotFoundError,
RepositoryConfigurationError,
RepositoryInvalidFileSystemError,
TransformError,
@@ -165,7 +166,16 @@ async def init(cls, commit: str | None = None, **kwargs: Any) -> Self:
log.info(f"Initialized the local directory for {self.name} because it was missing.")
if commit:
- self.get_commit_worktree(commit=commit)
+ try:
+ self.get_commit_worktree(commit=commit)
+ except CommitNotFoundError:
+ if not self.has_origin:
+ raise
+ # The commit may exist on the remote but not yet in this worker's clone.
+ # Fetch under the repository lock, which serializes shared-clone mutations, and retry.
+ async with lock.registry.get(name=self.name, namespace="repository"):
+ await self.fetch()
+ self.get_commit_worktree(commit=commit)
log.debug(
f"Initiated the object on an existing directory for {self.name}",
diff --git a/backend/infrahub/git/repository.py b/backend/infrahub/git/repository.py
index b1b364e02c7..5ef67ee73fb 100644
--- a/backend/infrahub/git/repository.py
+++ b/backend/infrahub/git/repository.py
@@ -1,5 +1,6 @@
from __future__ import annotations
+from dataclasses import dataclass
from typing import TYPE_CHECKING, Any
from cachetools import TTLCache
@@ -13,7 +14,7 @@
from infrahub import config
from infrahub.core.constants import InfrahubKind, RepositoryInternalStatus, RepositoryOperationalStatus
-from infrahub.exceptions import RepositoryError
+from infrahub.exceptions import CommitNotFoundError, RepositoryError
from infrahub.git.integrator import InfrahubRepositoryIntegrator
from infrahub.log import get_logger
@@ -23,6 +24,15 @@
log = get_logger()
+@dataclass
+class PendingObjectImport:
+ """A repository object import waiting to run: which commit to import from and which Infrahub branch to import into."""
+
+ infrahub_branch_name: str
+ commit: str
+ git_branch_name: str | None = None
+
+
class InfrahubRepository(InfrahubRepositoryIntegrator):
"""Primary type of Git repository, with deep integration within Infrahub.
@@ -66,7 +76,26 @@ async def sync(self, staging_branch: str | None = None) -> None:
By default the sync will focus only on the branches pulled from origin that have some differences with the local one.
Raises:
- GraphQLError: When creating a branch in the graph fails for a reason other than the branch already existing.
+ GraphQLError: When a branch or commit update against the database fails.
+
+ """
+ for pending in await self.collect_pending_imports(staging_branch=staging_branch):
+ await self.import_objects_from_files(
+ infrahub_branch_name=pending.infrahub_branch_name,
+ git_branch_name=pending.git_branch_name,
+ commit=pending.commit,
+ )
+
+ async def collect_pending_imports(self, staging_branch: str | None = None) -> list[PendingObjectImport]:
+ """Run the git and branch-setup side of a sync and return the imports it produced.
+
+ Brings the local clone in line with the remote and records the affected branches and their
+ commits in the database, pinning a per-commit worktree for each. Returns one entry per branch
+ whose objects still need importing into the graph. A per-branch git failure is logged and skipped so the
+ other branches' imports are still returned.
+
+ Raises:
+ GraphQLError: When a branch or commit update against the database fails.
"""
log.info("Starting the synchronization.", repository=self.name)
@@ -75,8 +104,9 @@ async def sync(self, staging_branch: str | None = None) -> None:
new_branches, updated_branches = await self.compare_local_remote()
+ pending_imports: list[PendingObjectImport] = []
if not new_branches and not updated_branches:
- return
+ return pending_imports
log.debug(f"New Branches {new_branches}, Updated Branches {updated_branches}", repository=self.name)
@@ -89,19 +119,30 @@ async def sync(self, staging_branch: str | None = None) -> None:
infrahub_branch = self._get_mapped_target_branch(branch_name=branch_name)
try:
- branch = await self.create_branch_in_graph(branch_name=infrahub_branch)
- except GraphQLError as exc:
- if "already exist" not in exc.errors[0]["message"]:
- raise
- branch = await self.sdk.branch.get(branch_name=infrahub_branch)
-
- await self.create_branch_in_git(branch_name=branch.name, branch_id=branch.id, push_origin=True)
-
- commit = self.get_commit_value(branch_name=branch_name, remote=False)
- self.create_commit_worktree(commit=commit)
- await self.update_commit_value(branch_name=infrahub_branch, commit=commit)
+ try:
+ branch = await self.create_branch_in_graph(branch_name=infrahub_branch)
+ except GraphQLError as exc:
+ if "already exist" not in exc.errors[0]["message"]:
+ raise
+ branch = await self.sdk.branch.get(branch_name=infrahub_branch)
+
+ await self.create_branch_in_git(branch_name=branch.name, branch_id=branch.id, push_origin=True)
+
+ commit = self.get_commit_value(branch_name=branch_name, remote=False)
+ self.create_commit_worktree(commit=commit)
+ await self.update_commit_value(branch_name=infrahub_branch, commit=commit)
+ except (RepositoryError, CommitNotFoundError, GitCommandError, ValueError) as exc:
+ # Isolate per-branch git failures so imports already collected for the other
+ # branches are still returned and applied.
+ log.warning(
+ "Failed to prepare branch for import, skipping it.",
+ repository=self.name,
+ branch=branch_name,
+ exc_info=exc,
+ )
+ continue
- await self.import_objects_from_files(infrahub_branch_name=infrahub_branch, commit=commit)
+ pending_imports.append(PendingObjectImport(infrahub_branch_name=infrahub_branch, commit=commit))
for branch_name in updated_branches:
is_valid = self.validate_remote_branch(branch_name=branch_name)
@@ -110,9 +151,23 @@ async def sync(self, staging_branch: str | None = None) -> None:
infrahub_branch = self._get_mapped_target_branch(branch_name=branch_name)
- commit_after = await self.pull(branch_name=branch_name)
+ try:
+ commit_after = await self.pull(branch_name=branch_name)
+ except (RepositoryError, CommitNotFoundError, GitCommandError, ValueError) as exc:
+ # Isolate per-branch git failures so imports already collected for the other
+ # branches are still returned and applied; graph errors are left to propagate.
+ log.warning(
+ "Failed to pull branch for import, skipping it.",
+ repository=self.name,
+ branch=branch_name,
+ exc_info=exc,
+ )
+ continue
+
if isinstance(commit_after, str):
- await self.import_objects_from_files(infrahub_branch_name=infrahub_branch, commit=commit_after)
+ pending_imports.append(
+ PendingObjectImport(infrahub_branch_name=infrahub_branch, commit=commit_after)
+ )
elif commit_after is True:
log.warning(
@@ -121,26 +176,36 @@ async def sync(self, staging_branch: str | None = None) -> None:
branch=branch_name,
)
- await self._sync_staging(staging_branch=staging_branch, updated_branches=updated_branches)
+ pending_imports.extend(
+ await self._collect_staging_imports(staging_branch=staging_branch, updated_branches=updated_branches)
+ )
+ return pending_imports
- async def _sync_staging(self, staging_branch: str | None, updated_branches: list[str]) -> None:
- if (
+ async def _collect_staging_imports(
+ self, staging_branch: str | None, updated_branches: list[str]
+ ) -> list[PendingObjectImport]:
+ if not (
self.internal_status == RepositoryInternalStatus.STAGING.value
and staging_branch
and self.default_branch in updated_branches
):
- commit_after = await self.pull(branch_name=self.default_branch)
- if isinstance(commit_after, str):
- await self.import_objects_from_files(
- git_branch_name=self.default_branch, infrahub_branch_name=staging_branch, commit=commit_after
- )
+ return []
- elif commit_after is True:
- log.warning(
- f"An update was detected but the commit remained the same after pull() ({commit_after}).",
- repository=self.name,
- branch=self.default_branch,
+ commit_after = await self.pull(branch_name=self.default_branch)
+ if isinstance(commit_after, str):
+ return [
+ PendingObjectImport(
+ infrahub_branch_name=staging_branch, git_branch_name=self.default_branch, commit=commit_after
)
+ ]
+
+ if commit_after is True:
+ log.warning(
+ f"An update was detected but the commit remained the same after pull() ({commit_after}).",
+ repository=self.name,
+ branch=self.default_branch,
+ )
+ return []
async def push(self, branch_name: str) -> bool:
"""Push a given branch to the remote Origin repository."""
diff --git a/backend/infrahub/git/sync.py b/backend/infrahub/git/sync.py
new file mode 100644
index 00000000000..50de2e681a4
--- /dev/null
+++ b/backend/infrahub/git/sync.py
@@ -0,0 +1,91 @@
+from __future__ import annotations
+
+from abc import ABC, abstractmethod
+from typing import TYPE_CHECKING
+
+from .repository import InfrahubRepository, PendingObjectImport
+
+if TYPE_CHECKING:
+ from infrahub_sdk.client import InfrahubClient
+
+ from infrahub.lock import InfrahubLockRegistry
+
+ from .models import GitRepositoryAdd
+
+
+class RepositoryImporter(ABC):
+ """Imports the objects of a single synced branch into the graph."""
+
+ @abstractmethod
+ async def import_branch(self, repo: InfrahubRepository, pending_import: PendingObjectImport) -> None: ...
+
+
+class RepositoryFileImporter(RepositoryImporter):
+ """Imports a branch by reading the object files of its pinned commit worktree."""
+
+ async def import_branch(self, repo: InfrahubRepository, pending_import: PendingObjectImport) -> None:
+ await repo.import_objects_from_files( # type: ignore[call-overload]
+ infrahub_branch_name=pending_import.infrahub_branch_name,
+ git_branch_name=pending_import.git_branch_name,
+ commit=pending_import.commit,
+ )
+
+
+class RepositoryAdder:
+ """Adds a new repository, holding the repository lock across the clone and the recording of its pinned commit.
+
+ The locked phase covers the clone, the default-branch worktree creation, and writing the pinned
+ commit back to the graph, which must stay consistent with each other. The default-branch object
+ import runs after the lock is released; it reads from the per-commit worktree pinned during the
+ locked phase, so it does not need the lock.
+ """
+
+ def __init__(
+ self, lock_registry: InfrahubLockRegistry, importer: RepositoryImporter, client: InfrahubClient
+ ) -> None:
+ self._lock_registry = lock_registry
+ self._importer = importer
+ self._client = client
+
+ async def add(self, model: GitRepositoryAdd) -> InfrahubRepository:
+ async with self._lock_registry.get(name=model.repository_name, namespace="repository"):
+ repo = await InfrahubRepository.new(
+ id=model.repository_id,
+ name=model.repository_name,
+ location=model.location,
+ client=self._client,
+ infrahub_branch_name=model.infrahub_branch_name,
+ internal_status=model.internal_status,
+ default_branch_name=model.default_branch_name,
+ )
+ default_commit = repo.get_commit_value(branch_name=repo.default_branch, remote=False)
+ repo.create_commit_worktree(commit=default_commit)
+
+ await self._importer.import_branch(
+ repo,
+ PendingObjectImport(
+ infrahub_branch_name=model.infrahub_branch_name,
+ git_branch_name=repo.default_branch,
+ commit=default_commit,
+ ),
+ )
+ return repo
+
+
+class RepositorySyncer:
+ """Synchronizes a repository, holding the repository lock only for the git working-copy mutations.
+
+ The lock serializes mutations of the repository's on-disk git state. The object import for each
+ synced branch runs after the lock is released; it reads from the per-commit worktree pinned during
+ the locked phase, so it does not need the lock.
+ """
+
+ def __init__(self, lock_registry: InfrahubLockRegistry, importer: RepositoryImporter) -> None:
+ self._lock_registry = lock_registry
+ self._importer = importer
+
+ async def sync(self, repo: InfrahubRepository, staging_branch: str | None = None) -> None:
+ async with self._lock_registry.get(name=repo.name, namespace="repository"):
+ pending_imports = await repo.collect_pending_imports(staging_branch=staging_branch)
+ for pending_import in pending_imports:
+ await self._importer.import_branch(repo, pending_import)
diff --git a/backend/infrahub/git/tasks.py b/backend/infrahub/git/tasks.py
index 64358d3aea5..5168e0d81be 100644
--- a/backend/infrahub/git/tasks.py
+++ b/backend/infrahub/git/tasks.py
@@ -25,7 +25,7 @@
)
from infrahub.core.manager import NodeManager
from infrahub.core.registry import registry
-from infrahub.exceptions import CheckError, RepositoryError
+from infrahub.exceptions import CheckError, CommitNotFoundError, RepositoryError
from infrahub.message_bus import Meta, messages
from infrahub.services.adapters.message_bus import InfrahubMessageBus
from infrahub.validators.tasks import start_validator
@@ -62,6 +62,7 @@
UserCheckDefinitionData,
)
from .repository import InfrahubReadOnlyRepository, InfrahubRepository, get_initialized_repo
+from .sync import RepositoryAdder, RepositoryFileImporter, RepositorySyncer
from .utils import fetch_artifact_definition_targets, fetch_check_definition_targets, get_repositories_commit_per_branch
@@ -87,40 +88,32 @@ def format_check_log_entry(entry: dict[str, Any]) -> str:
async def add_git_repository(model: GitRepositoryAdd) -> None:
await add_tags(branches=[model.infrahub_branch_name], nodes=[model.repository_id])
- async with lock.registry.get(name=model.repository_name, namespace="repository"):
- repo = await InfrahubRepository.new(
- id=model.repository_id,
- name=model.repository_name,
- location=model.location,
- client=get_client(),
- infrahub_branch_name=model.infrahub_branch_name,
- internal_status=model.internal_status,
- default_branch_name=model.default_branch_name,
- )
- await repo.import_objects_from_files( # type: ignore[call-overload]
- infrahub_branch_name=model.infrahub_branch_name, git_branch_name=model.default_branch_name
- )
- if model.internal_status == RepositoryInternalStatus.ACTIVE.value:
- await repo.sync()
+ importer = RepositoryFileImporter()
+ repo = await RepositoryAdder(lock_registry=lock.registry, importer=importer, client=get_client()).add(model)
- try:
- pinned_commit: str | None = repo.get_commit_value(branch_name=repo.default_branch, remote=False)
- except (ValueError, InvalidGitRepositoryError):
- pinned_commit = None
- # Notify other workers they need to clone the repository and check out the SHA pinned
- # by this initial sync, so the whole pool converges even if upstream advances meanwhile.
- notification = messages.RefreshGitFetch(
- meta=Meta(initiator_id=WORKER_IDENTITY, request_id=get_log_data().get("request_id", "")),
- location=model.location,
- repository_id=model.repository_id,
- repository_name=model.repository_name,
- repository_kind=InfrahubKind.REPOSITORY,
- infrahub_branch_name=model.infrahub_branch_name,
- infrahub_branch_id=model.infrahub_branch_id,
- commit=pinned_commit,
- )
- message_bus = await get_message_bus()
- await message_bus.send(message=notification)
+ if model.internal_status != RepositoryInternalStatus.ACTIVE.value:
+ return
+
+ await RepositorySyncer(lock_registry=lock.registry, importer=importer).sync(repo)
+
+ try:
+ pinned_commit: str | None = repo.get_commit_value(branch_name=repo.default_branch, remote=False)
+ except (ValueError, InvalidGitRepositoryError):
+ pinned_commit = None
+ # Notify other workers they need to clone the repository and check out the SHA pinned
+ # by this initial sync, so the whole pool converges even if upstream advances meanwhile.
+ notification = messages.RefreshGitFetch(
+ meta=Meta(initiator_id=WORKER_IDENTITY, request_id=get_log_data().get("request_id", "")),
+ location=model.location,
+ repository_id=model.repository_id,
+ repository_name=model.repository_name,
+ repository_kind=InfrahubKind.REPOSITORY,
+ infrahub_branch_name=model.infrahub_branch_name,
+ infrahub_branch_id=model.infrahub_branch_id,
+ commit=pinned_commit,
+ )
+ message_bus = await get_message_bus()
+ await message_bus.send(message=notification)
@flow(
@@ -228,9 +221,10 @@ async def sync_git_repo_with_origin_and_tag_on_failure(
default_branch_name=default_branch_name,
)
+ syncer = RepositorySyncer(lock_registry=lock.registry, importer=RepositoryFileImporter())
try:
- await repo.sync(staging_branch=staging_branch)
- except RepositoryError:
+ await syncer.sync(repo, staging_branch=staging_branch)
+ except (RepositoryError, CommitNotFoundError):
if operational_status == RepositoryOperationalStatus.ONLINE.value:
params: dict[str, Any] = {
"branches": [infrahub_branch] if infrahub_branch else [],
@@ -240,9 +234,124 @@ async def sync_git_repo_with_origin_and_tag_on_failure(
raise
+async def bootstrap_local_repository(
+ repo_name: str,
+ repository: CoreRepository,
+ active_internal_status: str,
+ infrahub_branch: str,
+ client: InfrahubClient,
+) -> InfrahubRepository | None:
+ """Ensure this worker has a usable local clone and seed the graph for a freshly created repo.
+
+ The repository lock covers the git working-copy mutations.
+ Returns None when the clone or the default-branch import fails and the repository should be
+ skipped.
+ """
+ log = get_run_logger()
+ default_import_git_branch: str | None = None
+ pinned_import_commit: str | None = None
+ async with lock.registry.get(name=repo_name, namespace="repository"):
+ init_failed = False
+ try:
+ repo = await InfrahubRepository.init(
+ id=repository.id,
+ name=repository.name.value,
+ location=repository.location.value,
+ client=client,
+ internal_status=active_internal_status,
+ default_branch_name=repository.default_branch.value,
+ )
+ except RepositoryError as exc:
+ get_logger().error(str(exc))
+ init_failed = True
+
+ if init_failed:
+ try:
+ repo = await InfrahubRepository.new(
+ id=repository.id,
+ name=repository.name.value,
+ location=repository.location.value,
+ client=client,
+ internal_status=active_internal_status,
+ default_branch_name=repository.default_branch.value,
+ )
+ except RepositoryError as exc:
+ log.info(exc.message)
+ return None
+ default_import_git_branch = registry.default_branch
+
+ if repo.reinitialized:
+ default_import_git_branch = repo.default_branch
+
+ if default_import_git_branch is not None:
+ # Pin the commit while the lock is held so the import below reads an immutable
+ # worktree even though it runs after the lock is released.
+ pinned_import_commit = repo.get_commit_value(branch_name=default_import_git_branch, remote=False)
+
+ if default_import_git_branch is not None:
+ try:
+ await repo.import_objects_from_files( # type: ignore[call-overload]
+ git_branch_name=default_import_git_branch,
+ infrahub_branch_name=infrahub_branch,
+ commit=pinned_import_commit,
+ )
+ except (RepositoryError, CommitNotFoundError) as exc:
+ log.info(exc.message)
+ return None
+
+ return repo
+
+
+async def sync_repository_from_origin(
+ repository: CoreRepository,
+ repo: InfrahubRepository,
+ active_internal_status: str,
+ staging_branch: str | None,
+ infrahub_branch: str,
+ infrahub_branch_id: str,
+ client: InfrahubClient,
+) -> None:
+ """Sync the repository from its origin and notify the worker pool of the resulting commit."""
+ log = get_run_logger()
+ try:
+ await sync_git_repo_with_origin_and_tag_on_failure(
+ client=client,
+ repository_id=repository.id,
+ repository_name=repository.name.value,
+ repository_location=repository.location.value,
+ internal_status=active_internal_status,
+ default_branch_name=repository.default_branch.value,
+ operational_status=repository.operational_status.value,
+ staging_branch=staging_branch,
+ infrahub_branch=infrahub_branch,
+ )
+ try:
+ pinned_commit: str | None = repo.get_commit_value(branch_name=infrahub_branch, remote=False)
+ except (ValueError, InvalidGitRepositoryError) as exc:
+ log.debug(
+ f"Could not resolve pinned commit for {repository.name.value}, workers will fall back to pull: {exc}"
+ )
+ pinned_commit = None
+ # Tell workers to fetch and check out the SHA pinned by this sync, so the whole
+ # pool converges on the same commit even if upstream advances during fan-out.
+ message = messages.RefreshGitFetch(
+ meta=Meta(initiator_id=WORKER_IDENTITY, request_id=get_log_data().get("request_id", "")),
+ location=repository.location.value,
+ repository_id=repository.id,
+ repository_name=repository.name.value,
+ repository_kind=repository.get_kind(),
+ infrahub_branch_name=infrahub_branch,
+ infrahub_branch_id=infrahub_branch_id,
+ commit=pinned_commit,
+ )
+ message_bus = await get_message_bus()
+ await message_bus.send(message=message)
+ except (RepositoryError, CommitNotFoundError) as exc:
+ log.info(exc.message)
+
+
@flow(name="git_repositories_sync", flow_run_name="Sync Git Repositories")
async def sync_remote_repositories() -> None:
- log = get_run_logger()
db = await get_database()
client = get_client()
@@ -263,80 +372,25 @@ async def sync_remote_repositories() -> None:
infrahub_branch = staging_branch or registry.default_branch
- async with lock.registry.get(name=repo_name, namespace="repository"):
- init_failed = False
- try:
- repo = await InfrahubRepository.init(
- id=repository.id,
- name=repository.name.value,
- location=repository.location.value,
- client=client,
- internal_status=active_internal_status,
- default_branch_name=repository.default_branch.value,
- )
- except RepositoryError as exc:
- get_logger().error(str(exc))
- init_failed = True
-
- if init_failed:
- try:
- repo = await InfrahubRepository.new(
- id=repository.id,
- name=repository.name.value,
- location=repository.location.value,
- client=client,
- internal_status=active_internal_status,
- default_branch_name=repository.default_branch.value,
- )
- await repo.import_objects_from_files( # type: ignore[call-overload]
- git_branch_name=registry.default_branch, infrahub_branch_name=infrahub_branch
- )
- except RepositoryError as exc:
- log.info(exc.message)
- continue
-
- if repo.reinitialized:
- try:
- await repo.import_objects_from_files( # type: ignore[call-overload]
- git_branch_name=repo.default_branch, infrahub_branch_name=infrahub_branch
- )
- except RepositoryError as exc:
- log.info(exc.message)
- continue
+ repo = await bootstrap_local_repository(
+ repo_name=repo_name,
+ repository=repository,
+ active_internal_status=active_internal_status,
+ infrahub_branch=infrahub_branch,
+ client=client,
+ )
+ if repo is None:
+ continue
- try:
- await sync_git_repo_with_origin_and_tag_on_failure(
- client=client,
- repository_id=repository.id,
- repository_name=repository.name.value,
- repository_location=repository.location.value,
- internal_status=active_internal_status,
- default_branch_name=repository.default_branch.value,
- operational_status=repository.operational_status.value,
- staging_branch=staging_branch,
- infrahub_branch=infrahub_branch,
- )
- try:
- pinned_commit: str | None = repo.get_commit_value(branch_name=infrahub_branch, remote=False)
- except (ValueError, InvalidGitRepositoryError) as exc:
- log.debug(f"Could not resolve pinned commit for {repo_name}, workers will fall back to pull: {exc}")
- pinned_commit = None
- # Tell workers to fetch and check out the SHA pinned by this sync, so the whole
- # pool converges on the same commit even if upstream advances during fan-out.
- message = messages.RefreshGitFetch(
- meta=Meta(initiator_id=WORKER_IDENTITY, request_id=get_log_data().get("request_id", "")),
- location=repository.location.value,
- repository_id=repository.id,
- repository_name=repository.name.value,
- repository_kind=repository.get_kind(),
- infrahub_branch_name=infrahub_branch,
- infrahub_branch_id=branches[infrahub_branch].id,
- commit=pinned_commit,
- )
- message_bus = await get_message_bus()
- await message_bus.send(message=message)
- except RepositoryError as exc:
- log.info(exc.message)
+ await sync_repository_from_origin(
+ repository=repository,
+ repo=repo,
+ active_internal_status=active_internal_status,
+ staging_branch=staging_branch,
+ infrahub_branch=infrahub_branch,
+ infrahub_branch_id=branches[infrahub_branch].id,
+ client=client,
+ )
@task( # type: ignore[arg-type]
diff --git a/backend/infrahub/proposed_change/tasks.py b/backend/infrahub/proposed_change/tasks.py
index 44f70c24dc3..70f0cba7fc7 100644
--- a/backend/infrahub/proposed_change/tasks.py
+++ b/backend/infrahub/proposed_change/tasks.py
@@ -1729,7 +1729,7 @@ async def _gather_repository_repository_diffs(
if repo.destination_branch:
files_changed, files_added, files_removed = await git_repo.calculate_diff_between_commits(
- first_commit=repo.source_commit, second_commit=repo.destination_commit
+ first_commit=repo.destination_commit, second_commit=repo.source_commit
)
else:
files_added = await git_repo.list_all_files(commit=repo.source_commit)
diff --git a/backend/tests/adapters/lock/__init__.py b/backend/tests/adapters/lock/__init__.py
new file mode 100644
index 00000000000..443af971e13
--- /dev/null
+++ b/backend/tests/adapters/lock/__init__.py
@@ -0,0 +1,13 @@
+from .mocks import RecordingImporter
+from .registry import RecordingLock, RecordingLockRegistry, install_recording_lock_registry
+from .timeline import LockAction, LockEvent, LockTimeline
+
+__all__ = [
+ "LockAction",
+ "LockEvent",
+ "LockTimeline",
+ "RecordingImporter",
+ "RecordingLock",
+ "RecordingLockRegistry",
+ "install_recording_lock_registry",
+]
diff --git a/backend/tests/adapters/lock/mocks/__init__.py b/backend/tests/adapters/lock/mocks/__init__.py
new file mode 100644
index 00000000000..4b4df72f06b
--- /dev/null
+++ b/backend/tests/adapters/lock/mocks/__init__.py
@@ -0,0 +1,3 @@
+from .importer import RecordingImporter
+
+__all__ = ["RecordingImporter"]
diff --git a/backend/tests/adapters/lock/mocks/importer.py b/backend/tests/adapters/lock/mocks/importer.py
new file mode 100644
index 00000000000..5c9c993ffb6
--- /dev/null
+++ b/backend/tests/adapters/lock/mocks/importer.py
@@ -0,0 +1,20 @@
+from __future__ import annotations
+
+from typing import TYPE_CHECKING
+
+from infrahub.git.sync import RepositoryImporter
+
+if TYPE_CHECKING:
+ from infrahub.git import InfrahubRepository
+ from infrahub.git.repository import PendingObjectImport
+ from tests.adapters.lock.timeline import LockTimeline
+
+
+class RecordingImporter(RepositoryImporter):
+ """Records a timeline checkpoint instead of importing, to capture the lock state at the import call."""
+
+ def __init__(self, timeline: LockTimeline) -> None:
+ self._timeline = timeline
+
+ async def import_branch(self, repo: InfrahubRepository, pending_import: PendingObjectImport) -> None:
+ self._timeline.checkpoint("import")
diff --git a/backend/tests/adapters/lock/registry.py b/backend/tests/adapters/lock/registry.py
new file mode 100644
index 00000000000..02dcb299572
--- /dev/null
+++ b/backend/tests/adapters/lock/registry.py
@@ -0,0 +1,75 @@
+from __future__ import annotations
+
+from typing import TYPE_CHECKING
+
+from infrahub import lock
+from infrahub.lock import InfrahubLock, InfrahubLockRegistry
+
+from .timeline import LockAction, LockTimeline
+
+if TYPE_CHECKING:
+ import redis.asyncio as redis
+
+ from infrahub.services import InfrahubServices
+
+
+class RecordingLock(InfrahubLock):
+ """A local lock that logs its real (non-re-entrant) acquire/release boundaries to a timeline."""
+
+ def __init__(
+ self,
+ name: str,
+ connection: redis.Redis | InfrahubServices | None = None,
+ in_multi: bool = False,
+ metrics: bool = True,
+ *,
+ timeline: LockTimeline,
+ ) -> None:
+ super().__init__(name=name, connection=connection, in_multi=in_multi, metrics=metrics)
+ self._timeline = timeline
+
+ async def acquire(self) -> None:
+ reentrant = self._recursion_var.get() is not None
+ await super().acquire()
+ if not reentrant:
+ self._timeline.record(self.name, LockAction.ACQUIRE)
+
+ async def release(self) -> None:
+ will_release = self._recursion_var.get() == 1
+ await super().release()
+ if will_release:
+ self._timeline.record(self.name, LockAction.RELEASE)
+
+
+class RecordingLockRegistry(InfrahubLockRegistry):
+ """Local-only lock registry that hands out ``RecordingLock`` instances backed by a shared timeline."""
+
+ def __init__(self, timeline: LockTimeline) -> None:
+ super().__init__(local_only=True)
+ self.timeline = timeline
+
+ def get(
+ self,
+ name: str,
+ namespace: str | None = None,
+ local: bool | None = None,
+ in_multi: bool = False,
+ metrics: bool = True,
+ ) -> InfrahubLock:
+ lock_name = self.name_generator.generate_name(name=name, namespace=namespace, local=local)
+ if lock_name not in self.locks:
+ self.locks[lock_name] = RecordingLock(
+ name=lock_name,
+ connection=self.connection,
+ in_multi=in_multi,
+ metrics=metrics,
+ timeline=self.timeline,
+ )
+ return self.locks[lock_name]
+
+
+def install_recording_lock_registry(timeline: LockTimeline | None = None) -> LockTimeline:
+ """Replace the global lock registry with a recording one and return its timeline."""
+ timeline = timeline or LockTimeline()
+ lock.registry = RecordingLockRegistry(timeline=timeline)
+ return timeline
diff --git a/backend/tests/adapters/lock/timeline.py b/backend/tests/adapters/lock/timeline.py
new file mode 100644
index 00000000000..fa9fc4b8626
--- /dev/null
+++ b/backend/tests/adapters/lock/timeline.py
@@ -0,0 +1,112 @@
+from __future__ import annotations
+
+import itertools
+from dataclasses import dataclass
+from enum import StrEnum
+from typing import TYPE_CHECKING
+
+if TYPE_CHECKING:
+ from collections.abc import Collection
+
+
+class LockAction(StrEnum):
+ ACQUIRE = "acquire"
+ RELEASE = "release"
+ CHECKPOINT = "checkpoint"
+
+
+@dataclass
+class LockEvent:
+ """A single entry in a lock timeline: a lock transition or an arbitrary checkpoint."""
+
+ seq: int
+ name: str
+ action: LockAction
+ label: str | None = None
+
+
+class LockTimeline:
+ """Ordered, monotonic log of lock transitions shared by every recorder in a test."""
+
+ def __init__(self) -> None:
+ self.events: list[LockEvent] = []
+ self._seq = itertools.count()
+
+ def record(self, name: str, action: LockAction, label: str | None = None) -> int:
+ seq = next(self._seq)
+ self.events.append(LockEvent(seq=seq, name=name, action=action, label=label))
+ return seq
+
+ def checkpoint(self, label: str) -> int:
+ """Mark a point of interest in the timeline so tests can ask which locks were held at it."""
+ return self.record(name=label, action=LockAction.CHECKPOINT, label=label)
+
+ def held_at(self, seq: int) -> set[str]:
+ """Return the set of lock names held at the moment ``seq`` was recorded."""
+ held: set[str] = set()
+ for event in self.events:
+ if event.seq >= seq:
+ break
+ if event.action == LockAction.ACQUIRE:
+ held.add(event.name)
+ elif event.action == LockAction.RELEASE:
+ held.discard(event.name)
+ return held
+
+ def currently_held(self) -> set[str]:
+ held: set[str] = set()
+ for event in self.events:
+ if event.action == LockAction.ACQUIRE:
+ held.add(event.name)
+ elif event.action == LockAction.RELEASE:
+ held.discard(event.name)
+ return held
+
+ def acquire_sequence(self, prefix: str | None = None) -> list[str]:
+ """Return the lock names in the order they were acquired, optionally filtered by name prefix."""
+ return [
+ event.name
+ for event in self.events
+ if event.action == LockAction.ACQUIRE and (prefix is None or event.name.startswith(prefix))
+ ]
+
+ def checkpoint_seqs(self, label: str) -> list[int]:
+ return [event.seq for event in self.events if event.action == LockAction.CHECKPOINT and event.label == label]
+
+ def assert_held_at_checkpoint(self, lock_name: str, label: str) -> None:
+ """Assert that ``lock_name`` was held at every checkpoint named ``label``."""
+ self._assert_held_at_checkpoint(lock_name, label, expected=True)
+
+ def assert_not_held_at_checkpoint(self, lock_name: str, label: str) -> None:
+ """Assert that ``lock_name`` was not held at any checkpoint named ``label``."""
+ self._assert_held_at_checkpoint(lock_name, label, expected=False)
+
+ def _assert_held_at_checkpoint(self, lock_name: str, label: str, *, expected: bool) -> None:
+ seqs = self.checkpoint_seqs(label)
+ if not seqs:
+ raise AssertionError(f"No checkpoint named {label!r} was recorded")
+ for seq in seqs:
+ actually_held = lock_name in self.held_at(seq)
+ if actually_held is not expected:
+ raise AssertionError(
+ f"At checkpoint {label!r}, expected lock {lock_name!r} held={expected} but held={actually_held}. "
+ f"Held at that point: {sorted(self.held_at(seq))}"
+ )
+
+ def assert_never_overlap(self, lock_names: Collection[str]) -> None:
+ """Assert that no two of ``lock_names`` were ever held at the same time.
+
+ Raises:
+ AssertionError: if two or more of ``lock_names`` were held simultaneously.
+
+ """
+ watched = set(lock_names)
+ held: set[str] = set()
+ for event in self.events:
+ if event.action == LockAction.ACQUIRE:
+ held.add(event.name)
+ overlap = held & watched
+ if len(overlap) > 1:
+ raise AssertionError(f"Locks {sorted(overlap)} were held simultaneously")
+ elif event.action == LockAction.RELEASE:
+ held.discard(event.name)
diff --git a/backend/tests/component/core/ipam/test_ipam_utilization.py b/backend/tests/component/core/ipam/test_ipam_utilization.py
index ee9e094c496..8f2bb230808 100644
--- a/backend/tests/component/core/ipam/test_ipam_utilization.py
+++ b/backend/tests/component/core/ipam/test_ipam_utilization.py
@@ -192,3 +192,16 @@ async def test_graphql_utilization_main_addition_after_branch_creation(
assert isinstance(branch_prefix, BuiltinIPPrefix)
branch_response = await branch_prefix.to_graphql(db=db, fields={"utilization": None})
assert branch_response["utilization"] == {"value": 3}
+
+
+async def test_get_children_deduplicates_repeated_prefixes(
+ db: InfrahubDatabase, default_branch: Branch, ip_dataset_01: dict[str, Node]
+) -> None:
+ net143 = ip_dataset_01["net143"]
+ utilization = PrefixUtilizationGetter(db=db, ip_prefixes=[net143], branch=default_branch)
+
+ single = await utilization.get_children(ip_prefixes=[net143])
+ duplicated = await utilization.get_children(ip_prefixes=[net143, net143])
+
+ assert len(single) > 0
+ assert len(duplicated) == len(single)
diff --git a/backend/tests/component/git/test_git_repository.py b/backend/tests/component/git/test_git_repository.py
index d507863ff0b..dce717e18dd 100644
--- a/backend/tests/component/git/test_git_repository.py
+++ b/backend/tests/component/git/test_git_repository.py
@@ -18,6 +18,7 @@
from infrahub.core.registry import registry
from infrahub.exceptions import (
CheckError,
+ CommitNotFoundError,
RepositoryError,
RepositoryFileNotFoundError,
RepositoryInvalidBranchError,
@@ -209,18 +210,51 @@ async def test_create_commit_worktree_wrong_commit(git_repo_01: InfrahubReposito
commit = "ffff1c0c64122bb2a7b208f7a9452146685bc7dd"
- with pytest.raises(GitCommandError, match="invalid reference"):
+ with pytest.raises(CommitNotFoundError, match=rf"Commit {commit} not found with GitRepository '{repo.name}'"):
repo.create_commit_worktree(commit=commit)
-async def test_create_commit_worktree_wrong_commit_no_origin(git_repo_01: InfrahubRepository) -> None:
+async def test_init_fetches_missing_commit_under_repo_lock(
+ git_repo_01: InfrahubRepository, git_upstream_repo_01: dict[str, str | Path]
+) -> None:
repo = git_repo_01
- repo.has_origin = False
+
+ # Add a commit to the upstream main after the local clone exists, without fetching it locally.
+ upstream = Repo(git_upstream_repo_01["path"])
+ first_file = find_first_file_in_directory(git_upstream_repo_01["path"])
+ assert first_file
+ async with await anyio.open_file(first_file, mode="a", encoding="utf-8") as file:
+ await file.write("new line\n")
+ upstream.index.add([first_file])
+ new_commit = str(upstream.index.commit("Change first file"))
+
+ # The local clone has not fetched the new commit, so the local primitive cannot find it.
+ with pytest.raises(CommitNotFoundError, match=rf"Commit {new_commit} not found with GitRepository '{repo.name}'"):
+ repo.create_commit_worktree(commit=new_commit)
+
+ # init() recovers by fetching the missing commit and materializing its worktree.
+ recovered = await InfrahubRepository.init(id=repo.id, name=repo.name, commit=new_commit, client=repo.client)
+ assert recovered.has_worktree(identifier=new_commit) is True
+
+
+async def test_init_missing_commit_without_origin_raises(git_repo_01: InfrahubRepository) -> None:
+ repo = git_repo_01
+ repo.get_git_repo_main().git.remote("remove", "origin")
commit = "ffff1c0c64122bb2a7b208f7a9452146685bc7dd"
- with pytest.raises(RepositoryError, match="no remote origin configured"):
- repo.create_commit_worktree(commit=commit)
+ with pytest.raises(CommitNotFoundError, match=rf"Commit {commit} not found with GitRepository '{repo.name}'"):
+ await InfrahubRepository.init(id=repo.id, name=repo.name, commit=commit, client=repo.client)
+
+
+async def test_init_missing_commit_absent_on_remote_raises(git_repo_01: InfrahubRepository) -> None:
+ repo = git_repo_01
+
+ commit = "ffff1c0c64122bb2a7b208f7a9452146685bc7dd"
+
+ # The commit exists neither locally nor on the remote, so init fetches once and still raises.
+ with pytest.raises(CommitNotFoundError, match=rf"Commit {commit} not found with GitRepository '{repo.name}'"):
+ await InfrahubRepository.init(id=repo.id, name=repo.name, commit=commit, client=repo.client)
async def test_get_worktrees(git_repo_01: InfrahubRepository) -> None:
@@ -940,8 +974,9 @@ async def test_calculate_diff_between_commits(
commit_branch01 = repo.get_commit_value(branch_name=branch01.name, remote=False)
commit_branch02 = repo.get_commit_value(branch_name=branch02.name, remote=False)
+ # branch02 is the base, branch01 holds the changes; first_commit is the old side, second_commit the new.
changed, added, removed = await repo.calculate_diff_between_commits(
- first_commit=commit_branch01, second_commit=commit_branch02
+ first_commit=commit_branch02, second_commit=commit_branch01
)
assert changed == ["README.md", "test_files/sports.yml"]
assert added == ["mynewfile.txt"]
diff --git a/backend/tests/component/git/test_git_rpc.py b/backend/tests/component/git/test_git_rpc.py
index 910c0fd67ca..56e731012b0 100644
--- a/backend/tests/component/git/test_git_rpc.py
+++ b/backend/tests/component/git/test_git_rpc.py
@@ -24,6 +24,7 @@
GitRepositoryPullReadOnly,
)
from infrahub.git.repository import InfrahubReadOnlyRepository
+from infrahub.git.sync import RepositoryAdder
from infrahub.git.tasks import add_git_repository, add_git_repository_read_only, pull_read_only
from infrahub.lock import InfrahubLockRegistry
from infrahub.message_bus.messages import RefreshGitFetch
@@ -31,6 +32,7 @@
from infrahub.services.adapters.workflow.local import WorkflowLocalExecution
from infrahub.workers.dependencies import build_client, build_message_bus, build_workflow
from infrahub.workflows.catalogue import GIT_REPOSITORIES_DIFF_NAMES_ONLY, GIT_REPOSITORIES_MERGE
+from tests.adapters.lock import LockTimeline, RecordingImporter, RecordingLockRegistry
from tests.adapters.message_bus import BusSimulator
from tests.helpers.test_client import dummy_async_request
@@ -79,7 +81,11 @@ async def setup(self, dependency_provider: Provider) -> AsyncGenerator[None, Non
patch.stopall()
async def test_git_rpc_create_successful(
- self, prefect_test_fixture: None, git_upstream_repo_01: dict[str, str], setup: None
+ self,
+ prefect_test_fixture: None,
+ git_upstream_repo_01: dict[str, str],
+ setup: None,
+ recording_lock_timeline: LockTimeline,
) -> None:
repo_id = str(UUIDT())
model = GitRepositoryAdd(
@@ -92,19 +98,15 @@ async def test_git_rpc_create_successful(
internal_status="active",
)
+ self.mock_repo.name = git_upstream_repo_01["name"]
self.mock_repo.import_objects_from_files = AsyncMock()
+ self.mock_repo.collect_pending_imports = AsyncMock(return_value=[])
- with (
- patch("infrahub.git.tasks.lock") as mock_infra_lock,
- patch("infrahub.git.tasks.InfrahubRepository", spec=InfrahubRepository) as mock_repo_class,
- ):
- mock_infra_lock.registry = AsyncMock(spec=InfrahubLockRegistry)
+ with patch("infrahub.git.sync.InfrahubRepository", spec=InfrahubRepository) as mock_repo_class:
mock_repo_class.new.return_value = self.mock_repo
await add_git_repository(model=model)
- mock_infra_lock.registry.get.assert_called_once_with(
- name=git_upstream_repo_01["name"], namespace="repository"
- )
+ assert f"repository.{git_upstream_repo_01['name']}" in recording_lock_timeline.acquire_sequence()
mock_repo_class.new.assert_awaited_once_with(
id=repo_id,
@@ -116,9 +118,10 @@ async def test_git_rpc_create_successful(
default_branch_name=self.default_branch_name,
)
self.mock_repo.import_objects_from_files.assert_awaited_once_with(
- infrahub_branch_name=self.default_branch_name, git_branch_name=self.default_branch_name
+ infrahub_branch_name=self.default_branch_name,
+ git_branch_name=self.default_branch_name,
+ commit="0123456789abcdef0123456789abcdef01234567",
)
- self.mock_repo.sync.assert_awaited_once_with()
assert len(self.recorder.messages) > 0
assert isinstance(self.recorder.messages[0], RefreshGitFetch)
@@ -380,3 +383,31 @@ async def test_new_repository(self, setup: None, prefect_test_fixture: None) ->
assert len(self.recorder.messages) > 0
assert isinstance(self.recorder.messages[0], RefreshGitFetch)
+
+
+@pytest.mark.usefixtures("git_repos_dir")
+async def test_add_git_repository_releases_lock_before_import(
+ prefect_test_fixture: None,
+ git_upstream_repo_01: dict[str, str],
+) -> None:
+ """The default-branch import must run after the repository lock held for the clone is released."""
+ timeline = LockTimeline()
+ client = InfrahubClient(config=Config(requester=dummy_async_request))
+ model = GitRepositoryAdd(
+ repository_id=str(UUIDT()),
+ repository_name=git_upstream_repo_01["name"],
+ location=str(git_upstream_repo_01["path"]),
+ default_branch_name="main",
+ infrahub_branch_name="main",
+ infrahub_branch_id=str(UUIDT()),
+ internal_status=RepositoryInternalStatus.INACTIVE.value,
+ )
+
+ adder = RepositoryAdder(
+ lock_registry=RecordingLockRegistry(timeline=timeline),
+ importer=RecordingImporter(timeline),
+ client=client,
+ )
+ await adder.add(model)
+
+ timeline.assert_not_held_at_checkpoint(f"repository.{git_upstream_repo_01['name']}", "import")
diff --git a/backend/tests/component/git/test_sync_lock_scope.py b/backend/tests/component/git/test_sync_lock_scope.py
new file mode 100644
index 00000000000..b1a74ca57c1
--- /dev/null
+++ b/backend/tests/component/git/test_sync_lock_scope.py
@@ -0,0 +1,29 @@
+from uuid import uuid4
+
+from infrahub.core.branch import Branch
+from infrahub.core.registry import registry
+from infrahub.git import InfrahubRepository
+from infrahub.git.sync import RepositorySyncer
+from tests.adapters.lock import LockTimeline, RecordingImporter, RecordingLockRegistry
+
+
+async def test_repository_lock_released_before_import(
+ prefect_test_fixture: None, git_repo_04: InfrahubRepository
+) -> None:
+ """The object import must not run while the repository lock is held.
+
+ The lock serializes mutations of the repository's on-disk git state. The import reads file
+ content from the per-commit worktree pinned earlier in the sync, so it stays outside that
+ critical section.
+ """
+ branch = Branch(name="branch01", uuid=uuid4())
+ registry.branch[branch.name] = branch
+
+ timeline = LockTimeline()
+ syncer = RepositorySyncer(
+ lock_registry=RecordingLockRegistry(timeline=timeline), importer=RecordingImporter(timeline)
+ )
+
+ await syncer.sync(git_repo_04)
+
+ timeline.assert_not_held_at_checkpoint(f"repository.{git_repo_04.name}", "import")
diff --git a/backend/tests/conftest.py b/backend/tests/conftest.py
index 1bfa535e0e5..d36e4e323e6 100644
--- a/backend/tests/conftest.py
+++ b/backend/tests/conftest.py
@@ -21,7 +21,7 @@
from testcontainers.core.container import DockerContainer
from testcontainers.core.waiting_utils import wait_for_logs
-from infrahub import config
+from infrahub import config, lock
from infrahub.config import load_and_exit
from infrahub.constants.database import Neo4jRuntime
from infrahub.core import registry
@@ -61,6 +61,7 @@
from infrahub.services import InfrahubServices
from infrahub.services.adapters.message_bus import InfrahubMessageBus
from infrahub.workers.dependencies import build_database, get_database
+from tests.adapters.lock import LockTimeline, install_recording_lock_registry
from tests.adapters.log import FakeLogger
from tests.adapters.message_bus import BusRecorder, BusSimulator
from tests.helpers.constants import (
@@ -1246,6 +1247,17 @@ def fake_log() -> FakeLogger:
return FakeLogger()
+@pytest.fixture
+def recording_lock_timeline() -> Generator[LockTimeline, None, None]:
+ """Swap the global lock registry for a recording one, restoring the original on teardown."""
+ original = lock.registry
+ timeline = install_recording_lock_registry()
+ try:
+ yield timeline
+ finally:
+ lock.registry = original
+
+
@pytest.fixture
def helper() -> TestHelper:
return TestHelper()
diff --git a/backend/tests/unit/adapters/__init__.py b/backend/tests/unit/adapters/__init__.py
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/backend/tests/unit/adapters/test_lock.py b/backend/tests/unit/adapters/test_lock.py
new file mode 100644
index 00000000000..525ec840467
--- /dev/null
+++ b/backend/tests/unit/adapters/test_lock.py
@@ -0,0 +1,62 @@
+from infrahub import lock
+from infrahub.lock import GLOBAL_GRAPH_LOCK, GLOBAL_SCHEMA_LOCK, LOCAL_SCHEMA_LOCK
+from tests.adapters.lock import LockAction, LockTimeline, RecordingLockRegistry
+
+
+async def test_records_acquire_and_release_order(recording_lock_timeline: LockTimeline) -> None:
+ async with lock.registry.get(name="repo-a", namespace="repository"):
+ pass
+ async with lock.registry.get(name="repo-b", namespace="repository"):
+ pass
+
+ assert recording_lock_timeline.acquire_sequence() == ["repository.repo-a", "repository.repo-b"]
+ assert [event.action for event in recording_lock_timeline.events] == [
+ LockAction.ACQUIRE,
+ LockAction.RELEASE,
+ LockAction.ACQUIRE,
+ LockAction.RELEASE,
+ ]
+ assert recording_lock_timeline.currently_held() == set()
+
+
+async def test_held_at_checkpoint(recording_lock_timeline: LockTimeline) -> None:
+ async with lock.registry.get(name="repo-a", namespace="repository"):
+ recording_lock_timeline.checkpoint("inside")
+ recording_lock_timeline.checkpoint("outside")
+
+ recording_lock_timeline.assert_held_at_checkpoint("repository.repo-a", "inside")
+ recording_lock_timeline.assert_not_held_at_checkpoint("repository.repo-a", "outside")
+
+
+async def test_reentrant_acquire_records_single_boundary(recording_lock_timeline: LockTimeline) -> None:
+ repo_lock = lock.registry.get(name="repo-a", namespace="repository")
+ async with repo_lock: # noqa: SIM117 - nesting is the re-entrant scenario under test
+ async with repo_lock:
+ recording_lock_timeline.checkpoint("nested")
+
+ assert recording_lock_timeline.acquire_sequence() == ["repository.repo-a"]
+ assert [event.action for event in recording_lock_timeline.events].count(LockAction.RELEASE) == 1
+ recording_lock_timeline.assert_held_at_checkpoint("repository.repo-a", "nested")
+
+
+async def test_multi_lock_records_each_member(recording_lock_timeline: LockTimeline) -> None:
+ async with lock.registry.global_graph_lock():
+ held = recording_lock_timeline.currently_held()
+
+ assert held == {LOCAL_SCHEMA_LOCK, GLOBAL_GRAPH_LOCK, GLOBAL_SCHEMA_LOCK}
+ assert recording_lock_timeline.currently_held() == set()
+
+
+async def test_assert_never_overlap(recording_lock_timeline: LockTimeline) -> None:
+ async with lock.registry.get(name="repo-a", namespace="repository"):
+ pass
+ async with lock.registry.get(name="repo-b", namespace="repository"):
+ pass
+
+ recording_lock_timeline.assert_never_overlap(["repository.repo-a", "repository.repo-b"])
+
+
+async def test_fixture_swaps_registry_and_exposes_timeline(recording_lock_timeline: LockTimeline) -> None:
+ assert isinstance(recording_lock_timeline, LockTimeline)
+ assert isinstance(lock.registry, RecordingLockRegistry)
+ assert lock.registry.timeline is recording_lock_timeline
diff --git a/backend/tests/unit/computed_attribute/test_tasks.py b/backend/tests/unit/computed_attribute/test_tasks.py
new file mode 100644
index 00000000000..6a99fe3c42f
--- /dev/null
+++ b/backend/tests/unit/computed_attribute/test_tasks.py
@@ -0,0 +1,46 @@
+import os
+from collections.abc import Iterator
+
+import pytest
+
+from infrahub.computed_attribute.tasks import (
+ _chunk_ids,
+ _get_submission_chunk_size,
+)
+
+ENV_VAR = "PREFECT_SERVER_EVENTS_MAXIMUM_RELATED_RESOURCES"
+
+
+@pytest.fixture
+def configured_max(request: pytest.FixtureRequest) -> Iterator[str]:
+ value: str = request.param
+ original = os.environ.get(ENV_VAR)
+ os.environ[ENV_VAR] = value
+ yield value
+ if original is None:
+ os.environ.pop(ENV_VAR, None)
+ else:
+ os.environ[ENV_VAR] = original
+
+
+def test_chunk_ids_rejects_zero_chunk_size() -> None:
+ """A zero chunk size is invalid and must never reach the chunker."""
+ with pytest.raises(ValueError, match="must not be zero"):
+ _chunk_ids(["a", "b"], 0)
+
+
+@pytest.mark.parametrize(
+ ("configured_max", "expected"),
+ [
+ ("1", 1), # 1 // 2 == 0 without the floor, which would break batching
+ ("2", 1),
+ ("10", 5),
+ ("500", 250),
+ ],
+ indirect=["configured_max"],
+)
+def test_submission_chunk_size_is_floored_at_one(configured_max: str, expected: int) -> None:
+ chunk_size = _get_submission_chunk_size()
+ assert chunk_size == expected
+ # The computed size is always usable by the chunker, never zero.
+ assert _chunk_ids(["a", "b", "c"], chunk_size)
diff --git a/changelog/+fix-computed-attribute-chunk-size.fixed.md b/changelog/+fix-computed-attribute-chunk-size.fixed.md
new file mode 100644
index 00000000000..9745364a47b
--- /dev/null
+++ b/changelog/+fix-computed-attribute-chunk-size.fixed.md
@@ -0,0 +1 @@
+Computed attribute updates no longer raise a `ValueError` when `PREFECT_SERVER_EVENTS_MAXIMUM_RELATED_RESOURCES` is set to `1`. The workflow submission chunk size is now floored at 1.
diff --git a/changelog/+link-artifact-validator-to-definition.added.md b/changelog/+link-artifact-validator-to-definition.added.md
new file mode 100644
index 00000000000..13d9cd34ef0
--- /dev/null
+++ b/changelog/+link-artifact-validator-to-definition.added.md
@@ -0,0 +1 @@
+Added a link in the Proposed Change "Checks" tab from each Artifact Validator to the originating `CoreArtifactDefinition`, so users can navigate directly to the definition that produced the check.
diff --git a/changelog/6639.fixed.md b/changelog/6639.fixed.md
new file mode 100644
index 00000000000..541b3542e16
--- /dev/null
+++ b/changelog/6639.fixed.md
@@ -0,0 +1 @@
+Narrowed the git repository lock so it only guards on-disk git working-copy mutations (clone, fetch, worktree creation) and no longer wraps importing repository objects into the graph.
diff --git a/changelog/9335.fixed.md b/changelog/9335.fixed.md
new file mode 100644
index 00000000000..7aa568e5968
--- /dev/null
+++ b/changelog/9335.fixed.md
@@ -0,0 +1 @@
+Added a refresh button to the artifact detail page so regenerated artifact content can be reloaded in place, without requiring a full browser refresh.
diff --git a/changelog/9530.fixed.md b/changelog/9530.fixed.md
new file mode 100644
index 00000000000..7843b6b3d78
--- /dev/null
+++ b/changelog/9530.fixed.md
@@ -0,0 +1 @@
+Fixed the file action reported by the repository diff so a file added on a branch is labelled `added` (and a deleted file `removed`) instead of being inverted in `GET /api/diff/files` and the Proposed Change Files tab.
diff --git a/dev/knowledge/backend/code-generation.md b/dev/knowledge/backend/code-generation.md
index 0c89380b1f9..7a4c2ce8fc7 100644
--- a/dev/knowledge/backend/code-generation.md
+++ b/dev/knowledge/backend/code-generation.md
@@ -70,7 +70,7 @@ CI runs these checks to ensure generated files are committed.
## Documentation Generation
-Reference documentation under `docs/docs/reference/` (and `docs/reference/configuration.mdx`) is rendered from the backend source, not written by hand:
+Reference documentation under `docs/docs/reference/` (and `docs/docs/reference/configuration.mdx`) is rendered from the backend source, not written by hand:
| Source | Output |
|--------|--------|
diff --git a/dev/knowledge/backend/testing.md b/dev/knowledge/backend/testing.md
index 243b172cea7..65b4ffde808 100644
--- a/dev/knowledge/backend/testing.md
+++ b/dev/knowledge/backend/testing.md
@@ -213,6 +213,7 @@ async def test_message_sent(bus_simulator: BusSimulator, ...):
- `MemoryCache` - In-memory cache for fast tests
- `FakeLogger` - Captures log output for assertions
+- `RecordingLockRegistry` / `LockTimeline` - Records lock acquire/release order so tests can assert what runs inside versus outside a critical section
## Supporting Directories
diff --git a/docs/package-lock.json b/docs/package-lock.json
index df7bab2c9ef..f62c97bb1cc 100644
--- a/docs/package-lock.json
+++ b/docs/package-lock.json
@@ -295,12 +295,12 @@
}
},
"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==",
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.7.tgz",
+ "integrity": "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==",
"license": "MIT",
"dependencies": {
- "@babel/helper-validator-identifier": "^7.28.5",
+ "@babel/helper-validator-identifier": "^7.29.7",
"js-tokens": "^4.0.0",
"picocolors": "^1.1.1"
},
@@ -357,13 +357,13 @@
}
},
"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==",
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.7.tgz",
+ "integrity": "sha512-DkXD5OJQaAQIdZ1bt3UZdEnHAn9Imd3IVBdX03UFe+ony9Ojw5pzr9YVKGDY1jt+Gcn/FnGkNf8r+Vj5NOJWtQ==",
"license": "MIT",
"dependencies": {
- "@babel/parser": "^7.29.0",
- "@babel/types": "^7.29.0",
+ "@babel/parser": "^7.29.7",
+ "@babel/types": "^7.29.7",
"@jridgewell/gen-mapping": "^0.3.12",
"@jridgewell/trace-mapping": "^0.3.28",
"jsesc": "^3.0.2"
@@ -482,9 +482,9 @@
}
},
"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==",
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.29.7.tgz",
+ "integrity": "sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==",
"license": "MIT",
"engines": {
"node": ">=6.9.0"
@@ -504,27 +504,27 @@
}
},
"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==",
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.29.7.tgz",
+ "integrity": "sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==",
"license": "MIT",
"dependencies": {
- "@babel/traverse": "^7.28.6",
- "@babel/types": "^7.28.6"
+ "@babel/traverse": "^7.29.7",
+ "@babel/types": "^7.29.7"
},
"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==",
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.29.7.tgz",
+ "integrity": "sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==",
"license": "MIT",
"dependencies": {
- "@babel/helper-module-imports": "^7.28.6",
- "@babel/helper-validator-identifier": "^7.28.5",
- "@babel/traverse": "^7.28.6"
+ "@babel/helper-module-imports": "^7.29.7",
+ "@babel/helper-validator-identifier": "^7.29.7",
+ "@babel/traverse": "^7.29.7"
},
"engines": {
"node": ">=6.9.0"
@@ -546,9 +546,9 @@
}
},
"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==",
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.29.7.tgz",
+ "integrity": "sha512-G7sHYigPY17oO5SYWnfD/0MTBwVR781S/JI643e/JhUYgVgWE/61SoW3NH9KWUKyKq5LVh3npif99Wkt6j86Jw==",
"license": "MIT",
"engines": {
"node": ">=6.9.0"
@@ -602,18 +602,18 @@
}
},
"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==",
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz",
+ "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==",
"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==",
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz",
+ "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==",
"license": "MIT",
"engines": {
"node": ">=6.9.0"
@@ -656,12 +656,12 @@
}
},
"node_modules/@babel/parser": {
- "version": "7.29.2",
- "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.2.tgz",
- "integrity": "sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA==",
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.7.tgz",
+ "integrity": "sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==",
"license": "MIT",
"dependencies": {
- "@babel/types": "^7.29.0"
+ "@babel/types": "^7.29.7"
},
"bin": {
"parser": "bin/babel-parser.js"
@@ -1246,15 +1246,15 @@
}
},
"node_modules/@babel/plugin-transform-modules-systemjs": {
- "version": "7.29.0",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.29.0.tgz",
- "integrity": "sha512-PrujnVFbOdUpw4UHiVwKvKRLMMic8+eC0CuNlxjsyZUiBjhFdPsewdXCkveh2KqBA9/waD0W1b4hXSOBQJezpQ==",
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.29.7.tgz",
+ "integrity": "sha512-TM2ZcQLoG2/y4HODiStCo10DibYhWhGWAwVv+EQKmG/7GFl0N+AAmUiXOMKM+aiJ9XBJ9AHVZBvTzMnJ2sM3cQ==",
"license": "MIT",
"dependencies": {
- "@babel/helper-module-transforms": "^7.28.6",
- "@babel/helper-plugin-utils": "^7.28.6",
- "@babel/helper-validator-identifier": "^7.28.5",
- "@babel/traverse": "^7.29.0"
+ "@babel/helper-module-transforms": "^7.29.7",
+ "@babel/helper-plugin-utils": "^7.29.7",
+ "@babel/helper-validator-identifier": "^7.29.7",
+ "@babel/traverse": "^7.29.7"
},
"engines": {
"node": ">=6.9.0"
@@ -1951,31 +1951,31 @@
}
},
"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==",
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.29.7.tgz",
+ "integrity": "sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==",
"license": "MIT",
"dependencies": {
- "@babel/code-frame": "^7.28.6",
- "@babel/parser": "^7.28.6",
- "@babel/types": "^7.28.6"
+ "@babel/code-frame": "^7.29.7",
+ "@babel/parser": "^7.29.7",
+ "@babel/types": "^7.29.7"
},
"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==",
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.7.tgz",
+ "integrity": "sha512-EhlfNQtZ+NK22w5BM61ciuiq1m58ed33Wr1Xan//ZRTy6hgjnwyCffRYwzsGXdASJSUJ1guZILsErh1eQcl+zw==",
"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",
+ "@babel/code-frame": "^7.29.7",
+ "@babel/generator": "^7.29.7",
+ "@babel/helper-globals": "^7.29.7",
+ "@babel/parser": "^7.29.7",
+ "@babel/template": "^7.29.7",
+ "@babel/types": "^7.29.7",
"debug": "^4.3.1"
},
"engines": {
@@ -1983,13 +1983,13 @@
}
},
"node_modules/@babel/types": {
- "version": "7.29.0",
- "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz",
- "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==",
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.7.tgz",
+ "integrity": "sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==",
"license": "MIT",
"dependencies": {
- "@babel/helper-string-parser": "^7.27.1",
- "@babel/helper-validator-identifier": "^7.28.5"
+ "@babel/helper-string-parser": "^7.29.7",
+ "@babel/helper-validator-identifier": "^7.29.7"
},
"engines": {
"node": ">=6.9.0"
@@ -2001,43 +2001,10 @@
"integrity": "sha512-i1L7noDNxtFyL5DmZafWy1wRVhGehQmzZaz1HiN5e7iylJMSZR7ekOV7NsIqa5qBldlLrsKv4HbgFUVlQrz8Mw==",
"license": "MIT"
},
- "node_modules/@chevrotain/cst-dts-gen": {
- "version": "11.1.1",
- "resolved": "https://registry.npmjs.org/@chevrotain/cst-dts-gen/-/cst-dts-gen-11.1.1.tgz",
- "integrity": "sha512-fRHyv6/f542qQqiRGalrfJl/evD39mAvbJLCekPazhiextEatq1Jx1K/i9gSd5NNO0ds03ek0Cbo/4uVKmOBcw==",
- "license": "Apache-2.0",
- "dependencies": {
- "@chevrotain/gast": "11.1.1",
- "@chevrotain/types": "11.1.1",
- "lodash-es": "4.17.23"
- }
- },
- "node_modules/@chevrotain/gast": {
- "version": "11.1.1",
- "resolved": "https://registry.npmjs.org/@chevrotain/gast/-/gast-11.1.1.tgz",
- "integrity": "sha512-Ko/5vPEYy1vn5CbCjjvnSO4U7GgxyGm+dfUZZJIWTlQFkXkyym0jFYrWEU10hyCjrA7rQtiHtBr0EaZqvHFZvg==",
- "license": "Apache-2.0",
- "dependencies": {
- "@chevrotain/types": "11.1.1",
- "lodash-es": "4.17.23"
- }
- },
- "node_modules/@chevrotain/regexp-to-ast": {
- "version": "11.1.1",
- "resolved": "https://registry.npmjs.org/@chevrotain/regexp-to-ast/-/regexp-to-ast-11.1.1.tgz",
- "integrity": "sha512-ctRw1OKSXkOrR8VTvOxrQ5USEc4sNrfwXHa1NuTcR7wre4YbjPcKw+82C2uylg/TEwFRgwLmbhlln4qkmDyteg==",
- "license": "Apache-2.0"
- },
"node_modules/@chevrotain/types": {
- "version": "11.1.1",
- "resolved": "https://registry.npmjs.org/@chevrotain/types/-/types-11.1.1.tgz",
- "integrity": "sha512-wb2ToxG8LkgPYnKe9FH8oGn3TMCBdnwiuNC5l5y+CtlaVRbCytU0kbVsk6CGrqTL4ZN4ksJa0TXOYbxpbthtqw==",
- "license": "Apache-2.0"
- },
- "node_modules/@chevrotain/utils": {
- "version": "11.1.1",
- "resolved": "https://registry.npmjs.org/@chevrotain/utils/-/utils-11.1.1.tgz",
- "integrity": "sha512-71eTYMzYXYSFPrbg/ZwftSaSDld7UYlS8OQa3lNnn9jzNtpFbaReRRyghzqS7rI3CDaorqpPJJcXGHK+FE1TVQ==",
+ "version": "11.1.2",
+ "resolved": "https://registry.npmjs.org/@chevrotain/types/-/types-11.1.2.tgz",
+ "integrity": "sha512-U+HFai5+zmJCkK86QsaJtoITlboZHBqrVketcO2ROv865xfCMSFpELQoz1GkX5GzME8pTa+3kbKrZHQtI0gdbw==",
"license": "Apache-2.0"
},
"node_modules/@colors/colors": {
@@ -4904,12 +4871,12 @@
}
},
"node_modules/@mermaid-js/parser": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/@mermaid-js/parser/-/parser-1.0.0.tgz",
- "integrity": "sha512-vvK0Hi/VWndxoh03Mmz6wa1KDriSPjS2XMZL/1l19HFwygiObEEoEwSDxOqyLzzAI6J2PU3261JjTMTO7x+BPw==",
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/@mermaid-js/parser/-/parser-1.1.1.tgz",
+ "integrity": "sha512-VuHdsYMK1bT6X2JbcAaWAhugTRvRBRyuZgd+c22swUeI9g/ntaxF7CY7dYarhZovofCbUNO0G7JesfmNtjYOCw==",
"license": "MIT",
"dependencies": {
- "langium": "^4.0.0"
+ "@chevrotain/types": "~11.1.1"
}
},
"node_modules/@mux/mux-data-google-ima": {
@@ -4992,6 +4959,18 @@
"@tybys/wasm-util": "^0.10.0"
}
},
+ "node_modules/@noble/hashes": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.4.0.tgz",
+ "integrity": "sha512-V1JJ1WTRUqHHrOSh597hURcMqVKVGL/ea3kv0gSnEdsEZ0/+VyPghM1lMNGc00z7CIQorSvbKpuJkxvuHbvdbg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 16"
+ },
+ "funding": {
+ "url": "https://paulmillr.com/funding/"
+ }
+ },
"node_modules/@node-rs/jieba": {
"version": "1.10.4",
"resolved": "https://registry.npmjs.org/@node-rs/jieba/-/jieba-1.10.4.tgz",
@@ -5280,6 +5259,163 @@
"node": ">= 8"
}
},
+ "node_modules/@peculiar/asn1-cms": {
+ "version": "2.7.0",
+ "resolved": "https://registry.npmjs.org/@peculiar/asn1-cms/-/asn1-cms-2.7.0.tgz",
+ "integrity": "sha512-hew63shtzzvBcSHbhm+cyAmKe6AIfinT9hzEqSPjDC6opTTMKmTkQ0gHuN2KsWlvqiKw1S/fS94fhag/FJkioQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@peculiar/asn1-schema": "^2.7.0",
+ "@peculiar/asn1-x509": "^2.7.0",
+ "@peculiar/asn1-x509-attr": "^2.7.0",
+ "asn1js": "^3.0.6",
+ "tslib": "^2.8.1"
+ }
+ },
+ "node_modules/@peculiar/asn1-csr": {
+ "version": "2.7.0",
+ "resolved": "https://registry.npmjs.org/@peculiar/asn1-csr/-/asn1-csr-2.7.0.tgz",
+ "integrity": "sha512-VVsAyGqErT9D1SY4aEqozThXMVI+ssVRiv2DDeYuvpBKLIgZ3hYs3Ay3u/VSoKq6ESFi9cf6rf3IOOzfwh7oMA==",
+ "license": "MIT",
+ "dependencies": {
+ "@peculiar/asn1-schema": "^2.7.0",
+ "@peculiar/asn1-x509": "^2.7.0",
+ "asn1js": "^3.0.6",
+ "tslib": "^2.8.1"
+ }
+ },
+ "node_modules/@peculiar/asn1-ecc": {
+ "version": "2.7.0",
+ "resolved": "https://registry.npmjs.org/@peculiar/asn1-ecc/-/asn1-ecc-2.7.0.tgz",
+ "integrity": "sha512-n7KEs/Q/wrB415cxy4fHOBhegp4NdJ15fkJPwcB/3/8iNBQC2L/N7SChJPKDJPZGYH0jD4Tg4/0vnHmwghnbKw==",
+ "license": "MIT",
+ "dependencies": {
+ "@peculiar/asn1-schema": "^2.7.0",
+ "@peculiar/asn1-x509": "^2.7.0",
+ "asn1js": "^3.0.6",
+ "tslib": "^2.8.1"
+ }
+ },
+ "node_modules/@peculiar/asn1-pfx": {
+ "version": "2.7.0",
+ "resolved": "https://registry.npmjs.org/@peculiar/asn1-pfx/-/asn1-pfx-2.7.0.tgz",
+ "integrity": "sha512-V/nrlQVmhg7lYAsM7E13UDL5erAwFv6kCIVFqNaMIHSVi7dngcT839JkRTkQBqznMG98l2XjxYk74ZztAohZzA==",
+ "license": "MIT",
+ "dependencies": {
+ "@peculiar/asn1-cms": "^2.7.0",
+ "@peculiar/asn1-pkcs8": "^2.7.0",
+ "@peculiar/asn1-rsa": "^2.7.0",
+ "@peculiar/asn1-schema": "^2.7.0",
+ "asn1js": "^3.0.6",
+ "tslib": "^2.8.1"
+ }
+ },
+ "node_modules/@peculiar/asn1-pkcs8": {
+ "version": "2.7.0",
+ "resolved": "https://registry.npmjs.org/@peculiar/asn1-pkcs8/-/asn1-pkcs8-2.7.0.tgz",
+ "integrity": "sha512-9GTl1nE8Mx1kTZ+7QyYatDyKsm34QcWRBFkY1iPvWC3X4Dona5s/tlLiQsx5WzVdZqiMBZNYT0buyw4/vbhnjw==",
+ "license": "MIT",
+ "dependencies": {
+ "@peculiar/asn1-schema": "^2.7.0",
+ "@peculiar/asn1-x509": "^2.7.0",
+ "asn1js": "^3.0.6",
+ "tslib": "^2.8.1"
+ }
+ },
+ "node_modules/@peculiar/asn1-pkcs9": {
+ "version": "2.7.0",
+ "resolved": "https://registry.npmjs.org/@peculiar/asn1-pkcs9/-/asn1-pkcs9-2.7.0.tgz",
+ "integrity": "sha512-Bh7m+OuIaSEllPQcSd9OSp93F4ROWH7sbITWV8MI+8dwsjE5111/87VxiWVvYFKyww3vp39geLv9ENqhwWHcew==",
+ "license": "MIT",
+ "dependencies": {
+ "@peculiar/asn1-cms": "^2.7.0",
+ "@peculiar/asn1-pfx": "^2.7.0",
+ "@peculiar/asn1-pkcs8": "^2.7.0",
+ "@peculiar/asn1-schema": "^2.7.0",
+ "@peculiar/asn1-x509": "^2.7.0",
+ "@peculiar/asn1-x509-attr": "^2.7.0",
+ "asn1js": "^3.0.6",
+ "tslib": "^2.8.1"
+ }
+ },
+ "node_modules/@peculiar/asn1-rsa": {
+ "version": "2.7.0",
+ "resolved": "https://registry.npmjs.org/@peculiar/asn1-rsa/-/asn1-rsa-2.7.0.tgz",
+ "integrity": "sha512-/qvENQrXyTZURjMqSeofHul0JJt2sNSzSwk36pl2olkHbaioMQgrASDZAlHXl0xUlnVbHj0uGgOrBMTb5x2aJQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@peculiar/asn1-schema": "^2.7.0",
+ "@peculiar/asn1-x509": "^2.7.0",
+ "asn1js": "^3.0.6",
+ "tslib": "^2.8.1"
+ }
+ },
+ "node_modules/@peculiar/asn1-schema": {
+ "version": "2.7.0",
+ "resolved": "https://registry.npmjs.org/@peculiar/asn1-schema/-/asn1-schema-2.7.0.tgz",
+ "integrity": "sha512-W8ZfWzLmQnrcky+eh3tni4IozMdqBDiHWU0N+vve/UGjMaUs8c0L7A2oEdkBXS8rTpWDpK/aoI3DG/L/hxmxPg==",
+ "license": "MIT",
+ "dependencies": {
+ "@peculiar/utils": "^2.0.2",
+ "asn1js": "^3.0.6",
+ "tslib": "^2.8.1"
+ }
+ },
+ "node_modules/@peculiar/asn1-x509": {
+ "version": "2.7.0",
+ "resolved": "https://registry.npmjs.org/@peculiar/asn1-x509/-/asn1-x509-2.7.0.tgz",
+ "integrity": "sha512-mUn9RRrkGDnG4ALfunDmzyRW5dg+sWCj/pfnCCqEHYbkGxEpvUt6iVJv8Yw1cyp6SWZ26ZE5oSmI5SqEaen15g==",
+ "license": "MIT",
+ "dependencies": {
+ "@peculiar/asn1-schema": "^2.7.0",
+ "@peculiar/utils": "^2.0.2",
+ "asn1js": "^3.0.6",
+ "tslib": "^2.8.1"
+ }
+ },
+ "node_modules/@peculiar/asn1-x509-attr": {
+ "version": "2.7.0",
+ "resolved": "https://registry.npmjs.org/@peculiar/asn1-x509-attr/-/asn1-x509-attr-2.7.0.tgz",
+ "integrity": "sha512-NS8e7SOgXipkzUPLF/sce7ukpMpWjhxYsH0n6Y+bHYo4TTxOb95Zv7hqwSuL212mj5YxovjdOKQOgH1As3E94w==",
+ "license": "MIT",
+ "dependencies": {
+ "@peculiar/asn1-schema": "^2.7.0",
+ "@peculiar/asn1-x509": "^2.7.0",
+ "asn1js": "^3.0.6",
+ "tslib": "^2.8.1"
+ }
+ },
+ "node_modules/@peculiar/utils": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/@peculiar/utils/-/utils-2.0.3.tgz",
+ "integrity": "sha512-+oL3HPFRIZ1St2K50lWCXiioIgSoxzz7R1J3uF6neO2yl1sgmpgY6XXJH4BdpoDkMWznQTeYF6oWNDZLCdQ4eQ==",
+ "license": "MIT",
+ "dependencies": {
+ "tslib": "^2.8.1"
+ }
+ },
+ "node_modules/@peculiar/x509": {
+ "version": "1.14.3",
+ "resolved": "https://registry.npmjs.org/@peculiar/x509/-/x509-1.14.3.tgz",
+ "integrity": "sha512-C2Xj8FZ0uHWeCXXqX5B4/gVFQmtSkiuOolzAgutjTfseNOHT3pUjljDZsTSxXFGgio54bCzVFqmEOUrIVk8RDA==",
+ "license": "MIT",
+ "dependencies": {
+ "@peculiar/asn1-cms": "^2.6.0",
+ "@peculiar/asn1-csr": "^2.6.0",
+ "@peculiar/asn1-ecc": "^2.6.0",
+ "@peculiar/asn1-pkcs9": "^2.6.0",
+ "@peculiar/asn1-rsa": "^2.6.0",
+ "@peculiar/asn1-schema": "^2.6.0",
+ "@peculiar/asn1-x509": "^2.6.0",
+ "pvtsutils": "^1.3.6",
+ "reflect-metadata": "^0.2.2",
+ "tslib": "^2.8.1",
+ "tsyringe": "^4.10.0"
+ },
+ "engines": {
+ "node": ">=20.0.0"
+ }
+ },
"node_modules/@pnpm/config.env-replace": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@pnpm/config.env-replace/-/config.env-replace-1.1.0.tgz",
@@ -6128,15 +6264,15 @@
}
},
"node_modules/@types/express": {
- "version": "4.17.23",
- "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.23.tgz",
- "integrity": "sha512-Crp6WY9aTYP3qPi2wGDo9iUe/rceX01UMhnF1jmwDcKCFM6cx7YhGP/Mpr3y9AASpfHixIG0E6azCcL5OcDHsQ==",
+ "version": "4.17.25",
+ "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.25.tgz",
+ "integrity": "sha512-dVd04UKsfpINUnK0yBoYHDF3xu7xVH4BuDotC/xGuycx4CgbP48X/KF/586bcObxT0HENHXEU8Nqtu6NR+eKhw==",
"license": "MIT",
"dependencies": {
"@types/body-parser": "*",
"@types/express-serve-static-core": "^4.17.33",
"@types/qs": "*",
- "@types/serve-static": "*"
+ "@types/serve-static": "^1"
}
},
"node_modules/@types/express-serve-static-core": {
@@ -6278,15 +6414,6 @@
"undici-types": "~7.14.0"
}
},
- "node_modules/@types/node-forge": {
- "version": "1.3.14",
- "resolved": "https://registry.npmjs.org/@types/node-forge/-/node-forge-1.3.14.tgz",
- "integrity": "sha512-mhVF2BnD4BO+jtOp7z1CdzaK4mbuK0LLQYAvdOLqHTavxFNq4zA1EmYkpnFjP8HOUzedfQkRnp0E2ulSAYSzAw==",
- "license": "MIT",
- "dependencies": {
- "@types/node": "*"
- }
- },
"node_modules/@types/prismjs": {
"version": "1.26.5",
"resolved": "https://registry.npmjs.org/@types/prismjs/-/prismjs-1.26.5.tgz",
@@ -6695,6 +6822,16 @@
"integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==",
"license": "ISC"
},
+ "node_modules/@upsetjs/venn.js": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/@upsetjs/venn.js/-/venn.js-2.0.0.tgz",
+ "integrity": "sha512-WbBhLrooyePuQ1VZxrJjtLvTc4NVfpOyKx0sKqioq9bX1C1m7Jgykkn8gLrtwumBioXIqam8DLxp88Adbue6Hw==",
+ "license": "MIT",
+ "optionalDependencies": {
+ "d3-selection": "^3.0.0",
+ "d3-transition": "^3.0.1"
+ }
+ },
"node_modules/@vimeo/player": {
"version": "2.29.0",
"resolved": "https://registry.npmjs.org/@vimeo/player/-/player-2.29.0.tgz",
@@ -7206,6 +7343,20 @@
"node": ">=8"
}
},
+ "node_modules/asn1js": {
+ "version": "3.0.10",
+ "resolved": "https://registry.npmjs.org/asn1js/-/asn1js-3.0.10.tgz",
+ "integrity": "sha512-S2s3aOytiKdFRdulw2qPE51MzjzVOisppcVv7jVFR+Kw0kxwvFrDcYA0h7Ndqbmj0HkMIXYWaoj7fli8kgx1eg==",
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "pvtsutils": "^1.3.6",
+ "pvutils": "^1.1.5",
+ "tslib": "^2.8.1"
+ },
+ "engines": {
+ "node": ">=12.0.0"
+ }
+ },
"node_modules/astring": {
"version": "1.9.0",
"resolved": "https://registry.npmjs.org/astring/-/astring-1.9.0.tgz",
@@ -7420,9 +7571,9 @@
}
},
"node_modules/body-parser": {
- "version": "1.20.4",
- "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.4.tgz",
- "integrity": "sha512-ZTgYYLMOXY9qKU/57FAo8F+HA2dGX7bqGc71txDRC1rS4frdFI5R7NhluHxH6M0YItAP0sHB4uqAOcYKxO6uGA==",
+ "version": "1.20.5",
+ "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.5.tgz",
+ "integrity": "sha512-3grm+/2tUOvu2cjJkvsIxrv/wVpfXQW4PsQHYm7yk4vfpu7Ekl6nEsYBoJUL6qDwZUx8wUhQ8tR2qz+ad9c9OA==",
"license": "MIT",
"dependencies": {
"bytes": "~3.1.2",
@@ -7433,7 +7584,7 @@
"http-errors": "~2.0.1",
"iconv-lite": "~0.4.24",
"on-finished": "~2.4.1",
- "qs": "~6.14.0",
+ "qs": "~6.15.1",
"raw-body": "~2.5.3",
"type-is": "~1.6.18",
"unpipe": "~1.0.0"
@@ -7547,9 +7698,9 @@
}
},
"node_modules/brace-expansion": {
- "version": "5.0.5",
- "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz",
- "integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==",
+ "version": "5.0.6",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.6.tgz",
+ "integrity": "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -7644,6 +7795,15 @@
"node": ">= 0.8"
}
},
+ "node_modules/bytestreamjs": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/bytestreamjs/-/bytestreamjs-2.0.1.tgz",
+ "integrity": "sha512-U1Z/ob71V/bXfVABvNr/Kumf5VyeQRBEm6Txb0PQ6S7V5GpBM3w4Cbqz/xPDicR5tN0uvDifng8C+5qECeGwyQ==",
+ "license": "BSD-3-Clause",
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
"node_modules/cacheable-lookup": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-7.0.0.tgz",
@@ -7912,32 +8072,6 @@
"url": "https://github.com/sponsors/fb55"
}
},
- "node_modules/chevrotain": {
- "version": "11.1.1",
- "resolved": "https://registry.npmjs.org/chevrotain/-/chevrotain-11.1.1.tgz",
- "integrity": "sha512-f0yv5CPKaFxfsPTBzX7vGuim4oIC1/gcS7LUGdBSwl2dU6+FON6LVUksdOo1qJjoUvXNn45urgh8C+0a24pACQ==",
- "license": "Apache-2.0",
- "dependencies": {
- "@chevrotain/cst-dts-gen": "11.1.1",
- "@chevrotain/gast": "11.1.1",
- "@chevrotain/regexp-to-ast": "11.1.1",
- "@chevrotain/types": "11.1.1",
- "@chevrotain/utils": "11.1.1",
- "lodash-es": "4.17.23"
- }
- },
- "node_modules/chevrotain-allstar": {
- "version": "0.3.1",
- "resolved": "https://registry.npmjs.org/chevrotain-allstar/-/chevrotain-allstar-0.3.1.tgz",
- "integrity": "sha512-b7g+y9A0v4mxCW1qUhf3BSVPg+/NvGErk/dOkrDaHA0nQIQGAtrOjlX//9OQtRlSCy+x9rfB5N8yC71lH1nvMw==",
- "license": "MIT",
- "dependencies": {
- "lodash-es": "^4.17.21"
- },
- "peerDependencies": {
- "chevrotain": "^11.0.0"
- }
- },
"node_modules/chokidar": {
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz",
@@ -9453,9 +9587,9 @@
}
},
"node_modules/dagre-d3-es": {
- "version": "7.0.13",
- "resolved": "https://registry.npmjs.org/dagre-d3-es/-/dagre-d3-es-7.0.13.tgz",
- "integrity": "sha512-efEhnxpSuwpYOKRm/L5KbqoZmNNukHa/Flty4Wp62JRvgH2ojwVgPgdYyr4twpieZnyRDdIH7PY2mopX26+j2Q==",
+ "version": "7.0.14",
+ "resolved": "https://registry.npmjs.org/dagre-d3-es/-/dagre-d3-es-7.0.14.tgz",
+ "integrity": "sha512-P4rFMVq9ESWqmOgK+dlXvOtLwYg0i7u0HBGJER0LZDJT2VHIPAMZ/riPxqJceWMStH5+E61QxFra9kIS3AqdMg==",
"license": "MIT",
"dependencies": {
"d3": "^7.9.0",
@@ -9498,9 +9632,9 @@
}
},
"node_modules/dayjs": {
- "version": "1.11.18",
- "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.18.tgz",
- "integrity": "sha512-zFBQ7WFRvVRhKcWoUh+ZA1g2HVgUbsZm9sbddh8EC5iv93sui8DVVz1Npvz+r6meo9VKfa8NyLWBsQK1VvIKPA==",
+ "version": "1.11.20",
+ "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.20.tgz",
+ "integrity": "sha512-YbwwqR/uYpeoP4pu043q+LTDLFBLApUP6VxRihdfNTqu4ubqMlGDLd6ErXhEgsyvY0K6nCs7nggYumAN+9uEuQ==",
"license": "MIT"
},
"node_modules/debounce": {
@@ -9672,9 +9806,9 @@
}
},
"node_modules/delaunator": {
- "version": "5.0.1",
- "resolved": "https://registry.npmjs.org/delaunator/-/delaunator-5.0.1.tgz",
- "integrity": "sha512-8nvh+XBe96aCESrGOqMp/84b13H9cdKbG5P2ejQCh4d4sK9RL4371qou9drQjMhvnPmhWl5hnmqbEE0fXr9Xnw==",
+ "version": "5.1.0",
+ "resolved": "https://registry.npmjs.org/delaunator/-/delaunator-5.1.0.tgz",
+ "integrity": "sha512-AGrQ4QSgssa1NGmWmLPqN5NY2KajF5MqxetNEO+o0n3ZwZZeTmt7bBnvzHWrmkZFxGgr4HdyFgelzgi06otLuQ==",
"license": "ISC",
"dependencies": {
"robust-predicates": "^3.0.2"
@@ -9819,9 +9953,9 @@
}
},
"node_modules/dompurify": {
- "version": "3.3.3",
- "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.3.3.tgz",
- "integrity": "sha512-Oj6pzI2+RqBfFG+qOaOLbFXLQ90ARpcGG6UePL82bJLtdsa6CYJD7nmiU8MW9nQNOtCHV3lZ/Bzq1X0QYbBZCA==",
+ "version": "3.4.9",
+ "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.4.9.tgz",
+ "integrity": "sha512-4dPSRMRDqHvs0V4YDFCsaIZo4if5u0xM+llyxiM2fwuZFdKArUBAF3VtI2+n8NKg9P870WMdYk0UhqQNoWXbfQ==",
"license": "(MPL-2.0 OR Apache-2.0)",
"optionalDependencies": {
"@types/trusted-types": "^2.0.7"
@@ -10036,6 +10170,16 @@
"node": ">= 0.4"
}
},
+ "node_modules/es-toolkit": {
+ "version": "1.46.1",
+ "resolved": "https://registry.npmjs.org/es-toolkit/-/es-toolkit-1.46.1.tgz",
+ "integrity": "sha512-5eNtXOs3tbfxXOj04tjjseeWkRWaoCjdEI+96DgwzZoe6c9juL49pXlzAFTI72aWC9Y8p7168g6XIKjh7k6pyQ==",
+ "license": "MIT",
+ "workspaces": [
+ "docs",
+ "benchmarks"
+ ]
+ },
"node_modules/esast-util-from-estree": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/esast-util-from-estree/-/esast-util-from-estree-2.0.0.tgz",
@@ -10551,14 +10695,14 @@
}
},
"node_modules/express": {
- "version": "4.22.1",
- "resolved": "https://registry.npmjs.org/express/-/express-4.22.1.tgz",
- "integrity": "sha512-F2X8g9P1X7uCPZMA3MVf9wcTqlyNp7IhH5qPCI0izhaOIYXaW9L535tGA3qmjRzpH+bZczqq7hVKxTR4NWnu+g==",
+ "version": "4.22.2",
+ "resolved": "https://registry.npmjs.org/express/-/express-4.22.2.tgz",
+ "integrity": "sha512-IuL+Elrou2ZvCFHs18/CIzy2Nzvo25nZ1/D2eIZlz7c+QUayAcYoiM2BthCjs+EBHVpjYjcuLDAiCWgeIX3X1Q==",
"license": "MIT",
"dependencies": {
"accepts": "~1.3.8",
"array-flatten": "1.1.1",
- "body-parser": "~1.20.3",
+ "body-parser": "~1.20.5",
"content-disposition": "~0.5.4",
"content-type": "~1.0.4",
"cookie": "~0.7.1",
@@ -10577,7 +10721,7 @@
"parseurl": "~1.3.3",
"path-to-regexp": "~0.1.12",
"proxy-addr": "~2.0.7",
- "qs": "~6.14.0",
+ "qs": "~6.15.1",
"range-parser": "~1.2.1",
"safe-buffer": "5.2.1",
"send": "~0.19.0",
@@ -10698,9 +10842,9 @@
"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==",
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.2.tgz",
+ "integrity": "sha512-rVjf7ArG3LTk+FS6Yw81V1DLuZl1bRbNrev6Tmd/9RaroeeRRJhAt7jg/6YFxbvAQXUCavSoZhPPj6oOx+5KjQ==",
"funding": [
{
"type": "github",
@@ -10943,9 +11087,9 @@
"license": "ISC"
},
"node_modules/follow-redirects": {
- "version": "1.15.11",
- "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz",
- "integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==",
+ "version": "1.16.0",
+ "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.16.0.tgz",
+ "integrity": "sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw==",
"funding": [
{
"type": "individual",
@@ -12579,9 +12723,9 @@
}
},
"node_modules/katex": {
- "version": "0.16.23",
- "resolved": "https://registry.npmjs.org/katex/-/katex-0.16.23.tgz",
- "integrity": "sha512-7VlC1hsEEolL9xNO05v9VjrvWZePkCVBJqj8ruICxYjZfHaHbaU53AlP+PODyFIXEnaEIEWi3wJy7FPZ95JAVg==",
+ "version": "0.16.47",
+ "resolved": "https://registry.npmjs.org/katex/-/katex-0.16.47.tgz",
+ "integrity": "sha512-Eeo8Ys1doU1z+x8AZsPpQu+p/QcZBI5PeOo7QGQdy2x2m0MU/hYagBbGOmXwr5KVbEfVuWv9LpnQWeehogurjg==",
"funding": [
"https://opencollective.com/katex",
"https://github.com/sponsors/katex"
@@ -12650,23 +12794,6 @@
"integrity": "sha512-Y+60/zizpJ3HRH8DCss+q95yr6145JXZo46OTpFvDZWLfRCE4qChOyk1b26nMaNpfHHgxagk9dXT5OP0Tfe+dQ==",
"license": "MIT"
},
- "node_modules/langium": {
- "version": "4.2.1",
- "resolved": "https://registry.npmjs.org/langium/-/langium-4.2.1.tgz",
- "integrity": "sha512-zu9QWmjpzJcomzdJQAHgDVhLGq5bLosVak1KVa40NzQHXfqr4eAHupvnPOVXEoLkg6Ocefvf/93d//SB7du4YQ==",
- "license": "MIT",
- "dependencies": {
- "chevrotain": "~11.1.1",
- "chevrotain-allstar": "~0.3.1",
- "vscode-languageserver": "~9.0.1",
- "vscode-languageserver-textdocument": "~1.0.11",
- "vscode-uri": "~3.1.0"
- },
- "engines": {
- "node": ">=20.10.0",
- "npm": ">=10.2.3"
- }
- },
"node_modules/latest-version": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/latest-version/-/latest-version-7.0.0.tgz",
@@ -12834,15 +12961,15 @@
}
},
"node_modules/lodash": {
- "version": "4.17.23",
- "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz",
- "integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==",
+ "version": "4.18.1",
+ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz",
+ "integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==",
"license": "MIT"
},
"node_modules/lodash-es": {
- "version": "4.17.23",
- "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.23.tgz",
- "integrity": "sha512-kVI48u3PZr38HdYz98UmfPnXl2DXrpdctLrFLCd3kOx1xUkOmpFPx7gCWWM5MPkL/fD8zb+Ph0QzjGFs4+hHWg==",
+ "version": "4.18.1",
+ "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.18.1.tgz",
+ "integrity": "sha512-J8xewKD/Gk22OZbhpOVSwcs60zhd95ESDwezOFuA3/099925PdHJ7OFHNTGtajL3AlZkykD32HykiMo+BIBI8A==",
"license": "MIT"
},
"node_modules/lodash.debounce": {
@@ -13733,31 +13860,32 @@
}
},
"node_modules/mermaid": {
- "version": "11.12.3",
- "resolved": "https://registry.npmjs.org/mermaid/-/mermaid-11.12.3.tgz",
- "integrity": "sha512-wN5ZSgJQIC+CHJut9xaKWsknLxaFBwCPwPkGTSUYrTiHORWvpT8RxGk849HPnpUAQ+/9BPRqYb80jTpearrHzQ==",
+ "version": "11.15.0",
+ "resolved": "https://registry.npmjs.org/mermaid/-/mermaid-11.15.0.tgz",
+ "integrity": "sha512-pTMbcf3rWdtLiYGpmoTjHEpeY8seiy6sR+9nD7LOs8KfUbHE4lOUAprTRqRAcWSQ6MQpdX+YEsxShtGsINtPtw==",
"license": "MIT",
"dependencies": {
"@braintree/sanitize-url": "^7.1.1",
- "@iconify/utils": "^3.0.1",
- "@mermaid-js/parser": "^1.0.0",
+ "@iconify/utils": "^3.0.2",
+ "@mermaid-js/parser": "^1.1.1",
"@types/d3": "^7.4.3",
- "cytoscape": "^3.29.3",
+ "@upsetjs/venn.js": "^2.0.0",
+ "cytoscape": "^3.33.1",
"cytoscape-cose-bilkent": "^4.1.0",
"cytoscape-fcose": "^2.2.0",
"d3": "^7.9.0",
"d3-sankey": "^0.12.3",
- "dagre-d3-es": "7.0.13",
- "dayjs": "^1.11.18",
- "dompurify": "^3.2.5",
- "katex": "^0.16.22",
+ "dagre-d3-es": "7.0.14",
+ "dayjs": "^1.11.19",
+ "dompurify": "^3.3.1",
+ "es-toolkit": "^1.45.1",
+ "katex": "^0.16.25",
"khroma": "^2.1.0",
- "lodash-es": "^4.17.23",
- "marked": "^16.2.1",
+ "marked": "^16.3.0",
"roughjs": "^4.6.6",
"stylis": "^4.3.6",
"ts-dedent": "^2.2.0",
- "uuid": "^11.1.0"
+ "uuid": "^11.1.0 || ^12 || ^13 || ^14.0.0"
}
},
"node_modules/methods": {
@@ -15814,9 +15942,9 @@
"license": "MIT"
},
"node_modules/nanoid": {
- "version": "3.3.11",
- "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz",
- "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==",
+ "version": "3.3.12",
+ "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.12.tgz",
+ "integrity": "sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==",
"funding": [
{
"type": "github",
@@ -15884,15 +16012,6 @@
"node": ">=18"
}
},
- "node_modules/node-forge": {
- "version": "1.4.0",
- "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.4.0.tgz",
- "integrity": "sha512-LarFH0+6VfriEhqMMcLX2F7SwSXeWwnEAJEsYm5QKWchiVYVvJyV9v7UDvUv+w5HO23ZpQTXDv/GxdDdMyOuoQ==",
- "license": "(BSD-3-Clause OR GPL-2.0)",
- "engines": {
- "node": ">= 6.13.0"
- }
- },
"node_modules/node-releases": {
"version": "2.0.27",
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz",
@@ -16590,6 +16709,23 @@
"pathe": "^2.0.3"
}
},
+ "node_modules/pkijs": {
+ "version": "3.4.0",
+ "resolved": "https://registry.npmjs.org/pkijs/-/pkijs-3.4.0.tgz",
+ "integrity": "sha512-emEcLuomt2j03vxD54giVB4SxTjnsqkU692xZOZXHDVoYyypEm+b3jpiTcc+Cf+myooc+/Ly0z01jqeNHVgJGw==",
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "@noble/hashes": "1.4.0",
+ "asn1js": "^3.0.6",
+ "bytestreamjs": "^2.0.1",
+ "pvtsutils": "^1.3.6",
+ "pvutils": "^1.1.3",
+ "tslib": "^2.8.1"
+ },
+ "engines": {
+ "node": ">=16.0.0"
+ }
+ },
"node_modules/player.style": {
"version": "0.3.1",
"resolved": "https://registry.npmjs.org/player.style/-/player.style-0.3.1.tgz",
@@ -16632,9 +16768,9 @@
}
},
"node_modules/postcss": {
- "version": "8.5.9",
- "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.9.tgz",
- "integrity": "sha512-7a70Nsot+EMX9fFU3064K/kdHWZqGVY+BADLyXc8Dfv+mTLLVl6JzJpPaCZ2kQL9gIJvKXSLMHhqdRRjwQeFtw==",
+ "version": "8.5.15",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.15.tgz",
+ "integrity": "sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==",
"funding": [
{
"type": "opencollective",
@@ -16651,7 +16787,7 @@
],
"license": "MIT",
"dependencies": {
- "nanoid": "^3.3.11",
+ "nanoid": "^3.3.12",
"picocolors": "^1.1.1",
"source-map-js": "^1.2.1"
},
@@ -18247,10 +18383,28 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/pvtsutils": {
+ "version": "1.3.6",
+ "resolved": "https://registry.npmjs.org/pvtsutils/-/pvtsutils-1.3.6.tgz",
+ "integrity": "sha512-PLgQXQ6H2FWCaeRak8vvk1GW462lMxB5s3Jm673N82zI4vqtVUPuZdffdZbPDFRoU8kAhItWFtPCWiPpp4/EDg==",
+ "license": "MIT",
+ "dependencies": {
+ "tslib": "^2.8.1"
+ }
+ },
+ "node_modules/pvutils": {
+ "version": "1.1.5",
+ "resolved": "https://registry.npmjs.org/pvutils/-/pvutils-1.1.5.tgz",
+ "integrity": "sha512-KTqnxsgGiQ6ZAzZCVlJH5eOjSnvlyEgx1m8bkRJfOhmGRqfo5KLvmAlACQkrjEtOQ4B7wF9TdSLIs9O90MX9xA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=16.0.0"
+ }
+ },
"node_modules/qs": {
- "version": "6.14.2",
- "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.2.tgz",
- "integrity": "sha512-V/yCWTTF7VJ9hIh18Ugr2zhJMP01MY7c5kh4J870L7imm6/DIzBsNLTXzMwUA3yZ5b/KBqLx8Kp3uRvd7xSe3Q==",
+ "version": "6.15.2",
+ "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.2.tgz",
+ "integrity": "sha512-Rzq0KEyX/w/tEybncDgdkZrJgVUsUMk3xjh3t5bv3S1HTAtg+uOYt72+ZfwiQwKdysThkTBdL/rTi6HDmX9Ddw==",
"license": "BSD-3-Clause",
"dependencies": {
"side-channel": "^1.1.0"
@@ -18711,6 +18865,12 @@
"url": "https://opencollective.com/unified"
}
},
+ "node_modules/reflect-metadata": {
+ "version": "0.2.2",
+ "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.2.2.tgz",
+ "integrity": "sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q==",
+ "license": "Apache-2.0"
+ },
"node_modules/regenerate": {
"version": "1.4.2",
"resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz",
@@ -19166,9 +19326,9 @@
}
},
"node_modules/robust-predicates": {
- "version": "3.0.2",
- "resolved": "https://registry.npmjs.org/robust-predicates/-/robust-predicates-3.0.2.tgz",
- "integrity": "sha512-IXgzBWvWQwE6PrDI05OvmXUIruQTcoMDzRsOd5CDvHCVLcLHMTSYvOK5Cm46kWqlV3yAbuSpBZdJ5oP5OUoStg==",
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/robust-predicates/-/robust-predicates-3.0.3.tgz",
+ "integrity": "sha512-NS3levdsRIUOmiJ8FZWCP7LG3QpJyrs/TE0Zpf1yvZu8cAJJ6QMW92H1c7kWpdIHo8RvmLxN/o2JXTKHp74lUA==",
"license": "Unlicense"
},
"node_modules/roughjs": {
@@ -19366,16 +19526,16 @@
"license": "MIT"
},
"node_modules/selfsigned": {
- "version": "2.4.1",
- "resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-2.4.1.tgz",
- "integrity": "sha512-th5B4L2U+eGLq1TVh7zNRGBapioSORUeymIydxgFpwww9d2qyKvtuPU2jJuHvYAwwqi2Y596QBL3eEqcPEYL8Q==",
+ "version": "5.5.0",
+ "resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-5.5.0.tgz",
+ "integrity": "sha512-ftnu3TW4+3eBfLRFnDEkzGxSF/10BJBkaLJuBHZX0kiPS7bRdlpZGu6YGt4KngMkdTwJE6MbjavFpqHvqVt+Ew==",
"license": "MIT",
"dependencies": {
- "@types/node-forge": "^1.3.0",
- "node-forge": "^1"
+ "@peculiar/x509": "^1.14.2",
+ "pkijs": "^3.3.3"
},
"engines": {
- "node": ">=10"
+ "node": ">=18"
}
},
"node_modules/semver": {
@@ -19670,9 +19830,9 @@
}
},
"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==",
+ "version": "1.8.4",
+ "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.4.tgz",
+ "integrity": "sha512-VsC6n6vz1ihYYyZZwX7YZSF5l5x36ca17OC+a69h94YqB7X6XLwf+5MOgynYir2SLFUbl8gIYvBo8K8RoNQ6bQ==",
"license": "MIT",
"engines": {
"node": ">= 0.4"
@@ -19682,14 +19842,14 @@
}
},
"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==",
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.1.tgz",
+ "integrity": "sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ==",
"license": "MIT",
"dependencies": {
"es-errors": "^1.3.0",
- "object-inspect": "^1.13.3",
- "side-channel-list": "^1.0.0",
+ "object-inspect": "^1.13.4",
+ "side-channel-list": "^1.0.1",
"side-channel-map": "^1.0.1",
"side-channel-weakmap": "^1.0.2"
},
@@ -19701,13 +19861,13 @@
}
},
"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==",
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.1.tgz",
+ "integrity": "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==",
"license": "MIT",
"dependencies": {
"es-errors": "^1.3.0",
- "object-inspect": "^1.13.3"
+ "object-inspect": "^1.13.4"
},
"engines": {
"node": ">= 0.4"
@@ -20527,6 +20687,24 @@
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
"license": "0BSD"
},
+ "node_modules/tsyringe": {
+ "version": "4.10.0",
+ "resolved": "https://registry.npmjs.org/tsyringe/-/tsyringe-4.10.0.tgz",
+ "integrity": "sha512-axr3IdNuVIxnaK5XGEUFTu3YmAQ6lllgrvqfEoR16g/HGnYY/6We4oWENtAnzK6/LpJ2ur9PAb80RBt7/U4ugw==",
+ "license": "MIT",
+ "dependencies": {
+ "tslib": "^1.9.3"
+ },
+ "engines": {
+ "node": ">= 6.0.0"
+ }
+ },
+ "node_modules/tsyringe/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/twitch-video-element": {
"version": "0.1.6",
"resolved": "https://registry.npmjs.org/twitch-video-element/-/twitch-video-element-0.1.6.tgz",
@@ -21074,16 +21252,16 @@
}
},
"node_modules/uuid": {
- "version": "11.1.0",
- "resolved": "https://registry.npmjs.org/uuid/-/uuid-11.1.0.tgz",
- "integrity": "sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A==",
+ "version": "14.0.0",
+ "resolved": "https://registry.npmjs.org/uuid/-/uuid-14.0.0.tgz",
+ "integrity": "sha512-Qo+uWgilfSmAhXCMav1uYFynlQO7fMFiMVZsQqZRMIXp0O7rR7qjkj+cPvBHLgBqi960QCoo/PH2/6ZtVqKvrg==",
"funding": [
"https://github.com/sponsors/broofa",
"https://github.com/sponsors/ctavan"
],
"license": "MIT",
"bin": {
- "uuid": "dist/esm/bin/uuid"
+ "uuid": "dist-node/bin/uuid"
}
},
"node_modules/value-equal": {
@@ -21153,55 +21331,6 @@
"media-played-ranges-mixin": "^0.1.0"
}
},
- "node_modules/vscode-jsonrpc": {
- "version": "8.2.0",
- "resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-8.2.0.tgz",
- "integrity": "sha512-C+r0eKJUIfiDIfwJhria30+TYWPtuHJXHtI7J0YlOmKAo7ogxP20T0zxB7HZQIFhIyvoBPwWskjxrvAtfjyZfA==",
- "license": "MIT",
- "engines": {
- "node": ">=14.0.0"
- }
- },
- "node_modules/vscode-languageserver": {
- "version": "9.0.1",
- "resolved": "https://registry.npmjs.org/vscode-languageserver/-/vscode-languageserver-9.0.1.tgz",
- "integrity": "sha512-woByF3PDpkHFUreUa7Hos7+pUWdeWMXRd26+ZX2A8cFx6v/JPTtd4/uN0/jB6XQHYaOlHbio03NTHCqrgG5n7g==",
- "license": "MIT",
- "dependencies": {
- "vscode-languageserver-protocol": "3.17.5"
- },
- "bin": {
- "installServerIntoExtension": "bin/installServerIntoExtension"
- }
- },
- "node_modules/vscode-languageserver-protocol": {
- "version": "3.17.5",
- "resolved": "https://registry.npmjs.org/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.17.5.tgz",
- "integrity": "sha512-mb1bvRJN8SVznADSGWM9u/b07H7Ecg0I3OgXDuLdn307rl/J3A9YD6/eYOssqhecL27hK1IPZAsaqh00i/Jljg==",
- "license": "MIT",
- "dependencies": {
- "vscode-jsonrpc": "8.2.0",
- "vscode-languageserver-types": "3.17.5"
- }
- },
- "node_modules/vscode-languageserver-textdocument": {
- "version": "1.0.12",
- "resolved": "https://registry.npmjs.org/vscode-languageserver-textdocument/-/vscode-languageserver-textdocument-1.0.12.tgz",
- "integrity": "sha512-cxWNPesCnQCcMPeenjKKsOCKQZ/L6Tv19DTRIGuLWe32lyzWhihGVJ/rcckZXJxfdKCFvRLS3fpBIsV/ZGX4zA==",
- "license": "MIT"
- },
- "node_modules/vscode-languageserver-types": {
- "version": "3.17.5",
- "resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.17.5.tgz",
- "integrity": "sha512-Ld1VelNuX9pdF39h2Hgaeb5hEZM2Z3jUrrMgWQAu82jMtZp7p3vJT3BzToKtZI7NgQssZje5o0zryOrhQvzQAg==",
- "license": "MIT"
- },
- "node_modules/vscode-uri": {
- "version": "3.1.0",
- "resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-3.1.0.tgz",
- "integrity": "sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ==",
- "license": "MIT"
- },
"node_modules/watchpack": {
"version": "2.5.1",
"resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.5.1.tgz",
@@ -21386,14 +21515,14 @@
}
},
"node_modules/webpack-dev-server": {
- "version": "5.2.2",
- "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-5.2.2.tgz",
- "integrity": "sha512-QcQ72gh8a+7JO63TAx/6XZf/CWhgMzu5m0QirvPfGvptOusAxG12w2+aua1Jkjr7hzaWDnJ2n6JFeexMHI+Zjg==",
+ "version": "5.2.4",
+ "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-5.2.4.tgz",
+ "integrity": "sha512-GqDPGZN9bRqKBTkp4aWkobDDHMsrXKoGSdOH56smIri8qR0JG8gfL8/v/f/OZR3/OKXjG8uwJbFVhKm/FNU/UA==",
"license": "MIT",
"dependencies": {
"@types/bonjour": "^3.5.13",
"@types/connect-history-api-fallback": "^1.5.4",
- "@types/express": "^4.17.21",
+ "@types/express": "^4.17.25",
"@types/express-serve-static-core": "^4.17.21",
"@types/serve-index": "^1.9.4",
"@types/serve-static": "^1.15.5",
@@ -21403,9 +21532,9 @@
"bonjour-service": "^1.2.1",
"chokidar": "^3.6.0",
"colorette": "^2.0.10",
- "compression": "^1.7.4",
+ "compression": "^1.8.1",
"connect-history-api-fallback": "^2.0.0",
- "express": "^4.21.2",
+ "express": "^4.22.1",
"graceful-fs": "^4.2.6",
"http-proxy-middleware": "^2.0.9",
"ipaddr.js": "^2.1.0",
@@ -21413,7 +21542,7 @@
"open": "^10.0.3",
"p-retry": "^6.2.0",
"schema-utils": "^4.2.0",
- "selfsigned": "^2.4.1",
+ "selfsigned": "^5.5.0",
"serve-index": "^1.9.1",
"sockjs": "^0.3.24",
"spdy": "^4.0.2",
@@ -21473,9 +21602,9 @@
}
},
"node_modules/webpack-dev-server/node_modules/ws": {
- "version": "8.18.3",
- "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz",
- "integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==",
+ "version": "8.20.1",
+ "resolved": "https://registry.npmjs.org/ws/-/ws-8.20.1.tgz",
+ "integrity": "sha512-It4dO0K5v//JtTXuPkfEOaI3uUN87iYPnqo/ZzqCoG3g8uhA66QUMs/SrM0YK7/NAu+r4LMh/9dq2A7k+rHs+w==",
"license": "MIT",
"engines": {
"node": ">=10.0.0"
diff --git a/frontend/app/package.json b/frontend/app/package.json
index ff86c2f93c2..6ae69998e95 100644
--- a/frontend/app/package.json
+++ b/frontend/app/package.json
@@ -88,7 +88,7 @@
"react-markdown": "^10.1.0",
"react-paginate": "^8.3.0",
"react-resizable-panels": "^4.10.0",
- "react-router": "^7.14.2",
+ "react-router": "^7.17.0",
"react-scan": "^0.5.3",
"react-simple-code-editor": "^0.14.1",
"react-syntax-highlighter": "^16.1.1",
diff --git a/frontend/app/pnpm-lock.yaml b/frontend/app/pnpm-lock.yaml
index 1b86951ad81..1d16ae5077e 100644
--- a/frontend/app/pnpm-lock.yaml
+++ b/frontend/app/pnpm-lock.yaml
@@ -14,7 +14,7 @@ importers:
dependencies:
'@apollo/client':
specifier: 3.13.8
- version: 3.13.8(@types/react@19.2.14)(graphql-ws@6.0.8(graphql@16.13.2)(ws@8.20.0))(graphql@16.13.2)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)
+ version: 3.13.8(@types/react@19.2.14)(graphql-ws@6.0.8(graphql@16.13.2)(ws@8.20.1))(graphql@16.13.2)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)
'@codemirror/commands':
specifier: ^6.10.3
version: 6.10.3
@@ -35,10 +35,10 @@ importers:
version: 6.41.1
'@graphiql/plugin-explorer':
specifier: ^5.1.1
- version: 5.1.1(@graphiql/react@0.37.3(@types/node@25.6.0)(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(graphql-ws@6.0.8(graphql@16.13.2)(ws@8.20.0))(graphql@16.13.2)(immer@11.1.4)(react-compiler-runtime@19.1.0-rc.1(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(use-sync-external-store@1.6.0(react@19.2.5)))(graphql@16.13.2)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)
+ version: 5.1.1(@graphiql/react@0.37.3(@types/node@25.6.0)(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(graphql-ws@6.0.8(graphql@16.13.2)(ws@8.20.1))(graphql@16.13.2)(immer@11.1.4)(react-compiler-runtime@19.1.0-rc.1(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(use-sync-external-store@1.6.0(react@19.2.5)))(graphql@16.13.2)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)
'@graphiql/toolkit':
specifier: ^0.11.3
- version: 0.11.3(@types/node@25.6.0)(graphql-ws@6.0.8(graphql@16.13.2)(ws@8.20.0))(graphql@16.13.2)
+ version: 0.11.3(@types/node@25.6.0)(graphql-ws@6.0.8(graphql@16.13.2)(ws@8.20.1))(graphql@16.13.2)
'@headlessui/react':
specifier: ^2.2.10
version: 2.2.10(react-dom@19.2.5(react@19.2.5))(react@19.2.5)
@@ -98,7 +98,7 @@ importers:
version: 6.0.1(@rolldown/plugin-babel@0.2.3(@babel/core@7.29.0)(@babel/runtime@7.29.2)(rolldown@1.0.0-rc.16)(vite@8.0.9(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(yaml@2.8.3)))(babel-plugin-react-compiler@1.0.0)(vite@8.0.9(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(yaml@2.8.3))
apollo-upload-client:
specifier: 18.0.1
- version: 18.0.1(@apollo/client@3.13.8(@types/react@19.2.14)(graphql-ws@6.0.8(graphql@16.13.2)(ws@8.20.0))(graphql@16.13.2)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(graphql@16.13.2)
+ version: 18.0.1(@apollo/client@3.13.8(@types/react@19.2.14)(graphql-ws@6.0.8(graphql@16.13.2)(ws@8.20.1))(graphql@16.13.2)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(graphql@16.13.2)
babel-plugin-react-compiler:
specifier: ^1.0.0
version: 1.0.0
@@ -125,7 +125,7 @@ importers:
version: 1.9.2(graphql@16.13.2)(typescript@5.9.3)
graphiql:
specifier: ^5.2.2
- version: 5.2.2(@types/node@25.6.0)(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(graphql-ws@6.0.8(graphql@16.13.2)(ws@8.20.0))(graphql@16.13.2)(immer@11.1.4)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(use-sync-external-store@1.6.0(react@19.2.5))
+ version: 5.2.2(@types/node@25.6.0)(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(graphql-ws@6.0.8(graphql@16.13.2)(ws@8.20.1))(graphql@16.13.2)(immer@11.1.4)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(use-sync-external-store@1.6.0(react@19.2.5))
graphql:
specifier: ^16.13.2
version: 16.13.2
@@ -149,7 +149,7 @@ importers:
version: 1.7.3(graphql@16.13.2)(monaco-editor@0.52.2)(prettier@3.8.3)
nuqs:
specifier: ^2.8.9
- version: 2.8.9(react-router@7.14.2(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(react@19.2.5)
+ version: 2.8.9(react-router@7.17.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(react@19.2.5)
openapi-fetch:
specifier: ^0.17.0
version: 0.17.0
@@ -187,8 +187,8 @@ importers:
specifier: ^4.10.0
version: 4.10.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5)
react-router:
- specifier: ^7.14.2
- version: 7.14.2(react-dom@19.2.5(react@19.2.5))(react@19.2.5)
+ specifier: ^7.17.0
+ version: 7.17.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5)
react-scan:
specifier: ^0.5.3
version: 0.5.3(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(rollup@4.60.0)
@@ -267,7 +267,7 @@ importers:
version: 1.60.0
'@types/apollo-upload-client':
specifier: 18.0.1
- version: 18.0.1(@types/react@19.2.14)(graphql-ws@6.0.8(graphql@16.13.2)(ws@8.20.0))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)
+ version: 18.0.1(@types/react@19.2.14)(graphql-ws@6.0.8(graphql@16.13.2)(ws@8.20.1))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)
'@types/node':
specifier: ^25.6.0
version: 25.6.0
@@ -504,28 +504,24 @@ packages:
engines: {node: '>=14.21.3'}
cpu: [arm64]
os: [linux]
- libc: [musl]
'@biomejs/cli-linux-arm64@2.4.12':
resolution: {integrity: sha512-tOwuCuZZtKi1jVzbk/5nXmIsziOB6yqN8c9r9QM0EJYPU6DpQWf11uBOSCfFKKM4H3d9ZoarvlgMfbcuD051Pw==}
engines: {node: '>=14.21.3'}
cpu: [arm64]
os: [linux]
- libc: [glibc]
'@biomejs/cli-linux-x64-musl@2.4.12':
resolution: {integrity: sha512-dwTIgZrGutzhkQCuvHynCkyW6hJxUuyZqKKO0YNfaS2GUoRO+tOvxXZqZB6SkWAOdfZTzwaw8IEdUnIkHKHoew==}
engines: {node: '>=14.21.3'}
cpu: [x64]
os: [linux]
- libc: [musl]
'@biomejs/cli-linux-x64@2.4.12':
resolution: {integrity: sha512-8pFeAnLU9QdW9jCIslB/v82bI0lhBmz2ZAKc8pVMFPO0t0wAHsoEkrUQUbMkIorTRIjbqyNZHA3lEXavsPWYSw==}
engines: {node: '>=14.21.3'}
cpu: [x64]
os: [linux]
- libc: [glibc]
'@biomejs/cli-win32-arm64@2.4.12':
resolution: {integrity: sha512-B0DLnx0vA9ya/3v7XyCaP+/lCpnbWbMOfUFFve+xb5OxyYvdHaS55YsSddr228Y+JAFk58agCuZTsqNiw2a6ig==}
@@ -1528,56 +1524,48 @@ packages:
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [arm64]
os: [linux]
- libc: [glibc]
'@oxc-parser/binding-linux-arm64-musl@0.126.0':
resolution: {integrity: sha512-FQ+MMh7MT0Dr/u8+RWmWKlfoeWPQyHDbhhxJShJlYtROXXPHsRs9EvmQOZZ3sx4Nn7JU8NX+oyw2YzQ7anBJcA==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [arm64]
os: [linux]
- libc: [musl]
'@oxc-parser/binding-linux-ppc64-gnu@0.126.0':
resolution: {integrity: sha512-Wv/T8C98hRQhGTlx2XFyLn5raRMp9U1lOQD+YnXNgAr7wHbJJpZ8mDBU7Rw+M3WytGcGTFcr6kqgfyQeHVtLbQ==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [ppc64]
os: [linux]
- libc: [glibc]
'@oxc-parser/binding-linux-riscv64-gnu@0.126.0':
resolution: {integrity: sha512-DHx1rT1zauW0ZbLHOiQh5AC9Xs3UkWx2XmfZHs+7nnWYr3sagrufoUQC+/XPwwjMIlCFXiFGM0sFh3TyOCZwqA==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [riscv64]
os: [linux]
- libc: [glibc]
'@oxc-parser/binding-linux-riscv64-musl@0.126.0':
resolution: {integrity: sha512-umDc2mTShH0U2zcEYf8mIJ163seLJNn54ZUZYeI5jD4qlg9izPwoLrC2aNPKlMJTu6u/ysmQWiEvIiaAG+INkw==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [riscv64]
os: [linux]
- libc: [musl]
'@oxc-parser/binding-linux-s390x-gnu@0.126.0':
resolution: {integrity: sha512-PXXeWayclRtO1pxQEeCpiqIglQdhK2mAI2VX5xnsWdImzSB5GpoQ8TNw7vTCKk2k+GZuxl+q1knncidjCyUP9w==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [s390x]
os: [linux]
- libc: [glibc]
'@oxc-parser/binding-linux-x64-gnu@0.126.0':
resolution: {integrity: sha512-wzocjxm34TbB3bFlqG65JiLtvf6ZDg2ZxRkLLbgXwDQUNU+0MPjQN8zy/0jBKNA5fnPLk3XeVdZ7Uin+7+CVkg==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [x64]
os: [linux]
- libc: [glibc]
'@oxc-parser/binding-linux-x64-musl@0.126.0':
resolution: {integrity: sha512-e83uftP60jmkPs2+CW6T6A1GYzN2H6IumDAiTntv9WyHR73PI3ImHNBkYqnA3ukeKI3xjcCbhSh9QeJWmufxGQ==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [x64]
os: [linux]
- libc: [musl]
'@oxc-parser/binding-openharmony-arm64@0.126.0':
resolution: {integrity: sha512-4WiOILHnPrTDY2/L4mE6PZCYwLN1d3ghma6BuTJ452CCgzRMt3uFplCtR+o3r9zdUWJYb370UizpI9CUcWXr1A==}
@@ -1650,49 +1638,41 @@ packages:
resolution: {integrity: sha512-heV2+jmXyYnUrpUXSPugqWDRpnsQcDm2AX4wzTuvgdlZfoNYO0O3W2AVpJYaDn9AG4JdM6Kxom8+foE7/BcSig==}
cpu: [arm64]
os: [linux]
- libc: [glibc]
'@oxc-resolver/binding-linux-arm64-musl@11.19.1':
resolution: {integrity: sha512-jvo2Pjs1c9KPxMuMPIeQsgu0mOJF9rEb3y3TdpsrqwxRM+AN6/nDDwv45n5ZrUnQMsdBy5gIabioMKnQfWo9ew==}
cpu: [arm64]
os: [linux]
- libc: [musl]
'@oxc-resolver/binding-linux-ppc64-gnu@11.19.1':
resolution: {integrity: sha512-vLmdNxWCdN7Uo5suays6A/+ywBby2PWBBPXctWPg5V0+eVuzsJxgAn6MMB4mPlshskYbppjpN2Zg83ArHze9gQ==}
cpu: [ppc64]
os: [linux]
- libc: [glibc]
'@oxc-resolver/binding-linux-riscv64-gnu@11.19.1':
resolution: {integrity: sha512-/b+WgR+VTSBxzgOhDO7TlMXC1ufPIMR6Vj1zN+/x+MnyXGW7prTLzU9eW85Aj7Th7CCEG9ArCbTeqxCzFWdg2w==}
cpu: [riscv64]
os: [linux]
- libc: [glibc]
'@oxc-resolver/binding-linux-riscv64-musl@11.19.1':
resolution: {integrity: sha512-YlRdeWb9j42p29ROh+h4eg/OQ3dTJlpHSa+84pUM9+p6i3djtPz1q55yLJhgW9XfDch7FN1pQ/Vd6YP+xfRIuw==}
cpu: [riscv64]
os: [linux]
- libc: [musl]
'@oxc-resolver/binding-linux-s390x-gnu@11.19.1':
resolution: {integrity: sha512-EDpafVOQWF8/MJynsjOGFThcqhRHy417sRyLfQmeiamJ8qVhSKAn2Dn2VVKUGCjVB9C46VGjhNo7nOPUi1x6uA==}
cpu: [s390x]
os: [linux]
- libc: [glibc]
'@oxc-resolver/binding-linux-x64-gnu@11.19.1':
resolution: {integrity: sha512-NxjZe+rqWhr+RT8/Ik+5ptA3oz7tUw361Wa5RWQXKnfqwSSHdHyrw6IdcTfYuml9dM856AlKWZIUXDmA9kkiBQ==}
cpu: [x64]
os: [linux]
- libc: [glibc]
'@oxc-resolver/binding-linux-x64-musl@11.19.1':
resolution: {integrity: sha512-cM/hQwsO3ReJg5kR+SpI69DMfvNCp+A/eVR4b4YClE5bVZwz8rh2Nh05InhwI5HR/9cArbEkzMjcKgTHS6UaNw==}
cpu: [x64]
os: [linux]
- libc: [musl]
'@oxc-resolver/binding-openharmony-arm64@11.19.1':
resolution: {integrity: sha512-QF080IowFB0+9Rh6RcD19bdgh49BpQHUW5TajG1qvWHvmrQznTZZjYlgE2ltLXyKY+qs4F/v5xuX1XS7Is+3qA==}
@@ -2262,42 +2242,36 @@ packages:
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [arm64]
os: [linux]
- libc: [glibc]
'@rolldown/binding-linux-arm64-musl@1.0.0-rc.16':
resolution: {integrity: sha512-3fPzdREH806oRLxpTWW1Gt4tQHs0TitZFOECB2xzCFLPKnSOy90gwA7P29cksYilFO6XVRY1kzga0cL2nRjKPg==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [arm64]
os: [linux]
- libc: [musl]
'@rolldown/binding-linux-ppc64-gnu@1.0.0-rc.16':
resolution: {integrity: sha512-EKwI1tSrLs7YVw+JPJT/G2dJQ1jl9qlTTTEG0V2Ok/RdOenRfBw2PQdLPyjhIu58ocdBfP7vIRN/pvMsPxs/AQ==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [ppc64]
os: [linux]
- libc: [glibc]
'@rolldown/binding-linux-s390x-gnu@1.0.0-rc.16':
resolution: {integrity: sha512-Uknladnb3Sxqu6SEcqBldQyJUpk8NleooZEc0MbRBJ4inEhRYWZX0NJu12vNf2mqAq7gsofAxHrGghiUYjhaLQ==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [s390x]
os: [linux]
- libc: [glibc]
'@rolldown/binding-linux-x64-gnu@1.0.0-rc.16':
resolution: {integrity: sha512-FIb8+uG49sZBtLTn+zt1AJ20TqVcqWeSIyoVt0or7uAWesgKaHbiBh6OpA/k9v0LTt+PTrb1Lao133kP4uVxkg==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [x64]
os: [linux]
- libc: [glibc]
'@rolldown/binding-linux-x64-musl@1.0.0-rc.16':
resolution: {integrity: sha512-RuERhF9/EgWxZEXYWCOaViUWHIboceK4/ivdtQ3R0T44NjLkIIlGIAVAuCddFxsZ7vnRHtNQUrt2vR2n2slB2w==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [x64]
os: [linux]
- libc: [musl]
'@rolldown/binding-openharmony-arm64@1.0.0-rc.16':
resolution: {integrity: sha512-mXcXnvd9GpazCxeUCCnZ2+YF7nut+ZOEbE4GtaiPtyY6AkhZWbK70y1KK3j+RDhjVq5+U8FySkKRb/+w0EeUwA==}
@@ -2388,79 +2362,66 @@ packages:
resolution: {integrity: sha512-RzeBwv0B3qtVBWtcuABtSuCzToo2IEAIQrcyB/b2zMvBWVbjo8bZDjACUpnaafaxhTw2W+imQbP2BD1usasK4g==}
cpu: [arm]
os: [linux]
- libc: [glibc]
'@rollup/rollup-linux-arm-musleabihf@4.60.0':
resolution: {integrity: sha512-Sf7zusNI2CIU1HLzuu9Tc5YGAHEZs5Lu7N1ssJG4Tkw6e0MEsN7NdjUDDfGNHy2IU+ENyWT+L2obgWiguWibWQ==}
cpu: [arm]
os: [linux]
- libc: [musl]
'@rollup/rollup-linux-arm64-gnu@4.60.0':
resolution: {integrity: sha512-DX2x7CMcrJzsE91q7/O02IJQ5/aLkVtYFryqCjduJhUfGKG6yJV8hxaw8pZa93lLEpPTP/ohdN4wFz7yp/ry9A==}
cpu: [arm64]
os: [linux]
- libc: [glibc]
'@rollup/rollup-linux-arm64-musl@4.60.0':
resolution: {integrity: sha512-09EL+yFVbJZlhcQfShpswwRZ0Rg+z/CsSELFCnPt3iK+iqwGsI4zht3secj5vLEs957QvFFXnzAT0FFPIxSrkQ==}
cpu: [arm64]
os: [linux]
- libc: [musl]
'@rollup/rollup-linux-loong64-gnu@4.60.0':
resolution: {integrity: sha512-i9IcCMPr3EXm8EQg5jnja0Zyc1iFxJjZWlb4wr7U2Wx/GrddOuEafxRdMPRYVaXjgbhvqalp6np07hN1w9kAKw==}
cpu: [loong64]
os: [linux]
- libc: [glibc]
'@rollup/rollup-linux-loong64-musl@4.60.0':
resolution: {integrity: sha512-DGzdJK9kyJ+B78MCkWeGnpXJ91tK/iKA6HwHxF4TAlPIY7GXEvMe8hBFRgdrR9Ly4qebR/7gfUs9y2IoaVEyog==}
cpu: [loong64]
os: [linux]
- libc: [musl]
'@rollup/rollup-linux-ppc64-gnu@4.60.0':
resolution: {integrity: sha512-RwpnLsqC8qbS8z1H1AxBA1H6qknR4YpPR9w2XX0vo2Sz10miu57PkNcnHVaZkbqyw/kUWfKMI73jhmfi9BRMUQ==}
cpu: [ppc64]
os: [linux]
- libc: [glibc]
'@rollup/rollup-linux-ppc64-musl@4.60.0':
resolution: {integrity: sha512-Z8pPf54Ly3aqtdWC3G4rFigZgNvd+qJlOE52fmko3KST9SoGfAdSRCwyoyG05q1HrrAblLbk1/PSIV+80/pxLg==}
cpu: [ppc64]
os: [linux]
- libc: [musl]
'@rollup/rollup-linux-riscv64-gnu@4.60.0':
resolution: {integrity: sha512-3a3qQustp3COCGvnP4SvrMHnPQ9d1vzCakQVRTliaz8cIp/wULGjiGpbcqrkv0WrHTEp8bQD/B3HBjzujVWLOA==}
cpu: [riscv64]
os: [linux]
- libc: [glibc]
'@rollup/rollup-linux-riscv64-musl@4.60.0':
resolution: {integrity: sha512-pjZDsVH/1VsghMJ2/kAaxt6dL0psT6ZexQVrijczOf+PeP2BUqTHYejk3l6TlPRydggINOeNRhvpLa0AYpCWSQ==}
cpu: [riscv64]
os: [linux]
- libc: [musl]
'@rollup/rollup-linux-s390x-gnu@4.60.0':
resolution: {integrity: sha512-3ObQs0BhvPgiUVZrN7gqCSvmFuMWvWvsjG5ayJ3Lraqv+2KhOsp+pUbigqbeWqueGIsnn+09HBw27rJ+gYK4VQ==}
cpu: [s390x]
os: [linux]
- libc: [glibc]
'@rollup/rollup-linux-x64-gnu@4.60.0':
resolution: {integrity: sha512-EtylprDtQPdS5rXvAayrNDYoJhIz1/vzN2fEubo3yLE7tfAw+948dO0g4M0vkTVFhKojnF+n6C8bDNe+gDRdTg==}
cpu: [x64]
os: [linux]
- libc: [glibc]
'@rollup/rollup-linux-x64-musl@4.60.0':
resolution: {integrity: sha512-k09oiRCi/bHU9UVFqD17r3eJR9bn03TyKraCrlz5ULFJGdJGi7VOmm9jl44vOJvRJ6P7WuBi/s2A97LxxHGIdw==}
cpu: [x64]
os: [linux]
- libc: [musl]
'@rollup/rollup-openbsd-x64@4.60.0':
resolution: {integrity: sha512-1o/0/pIhozoSaDJoDcec+IVLbnRtQmHwPV730+AOD29lHEEo4F5BEUB24H0OBdhbBBDwIOSuf7vgg0Ywxdfiiw==}
@@ -2616,28 +2577,24 @@ packages:
engines: {node: '>= 20'}
cpu: [arm64]
os: [linux]
- libc: [glibc]
'@tailwindcss/oxide-linux-arm64-musl@4.2.4':
resolution: {integrity: sha512-bBADEGAbo4ASnppIziaQJelekCxdMaxisrk+fB7Thit72IBnALp9K6ffA2G4ruj90G9XRS2VQ6q2bCKbfFV82g==}
engines: {node: '>= 20'}
cpu: [arm64]
os: [linux]
- libc: [musl]
'@tailwindcss/oxide-linux-x64-gnu@4.2.4':
resolution: {integrity: sha512-7Mx25E4WTfnht0TVRTyC00j3i0M+EeFe7wguMDTlX4mRxafznw0CA8WJkFjWYH5BlgELd1kSjuU2JiPnNZbJDA==}
engines: {node: '>= 20'}
cpu: [x64]
os: [linux]
- libc: [glibc]
'@tailwindcss/oxide-linux-x64-musl@4.2.4':
resolution: {integrity: sha512-2wwJRF7nyhOR0hhHoChc04xngV3iS+akccHTGtz965FwF0up4b2lOdo6kI1EbDaEXKgvcrFBYcYQQ/rrnWFVfA==}
engines: {node: '>= 20'}
cpu: [x64]
os: [linux]
- libc: [musl]
'@tailwindcss/oxide-wasm32-wasi@4.2.4':
resolution: {integrity: sha512-FQsqApeor8Fo6gUEklzmaa9994orJZZDBAlQpK2Mq+DslRKFJeD6AjHpBQ0kZFQohVr8o85PPh8eOy86VlSCmw==}
@@ -2983,6 +2940,7 @@ packages:
'@ungap/structured-clone@1.3.0':
resolution: {integrity: sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==}
+ deprecated: Potential CWE-502 - Update to 1.3.1 or higher
'@vitejs/plugin-react@6.0.1':
resolution: {integrity: sha512-l9X/E3cDb+xY3SWzlG1MOGt2usfEHGMNIaegaUGFsLkb3RCn/k8/TOXBcab+OndDI4TBtktT8/9BwwW8Vi9KUQ==}
@@ -4167,28 +4125,24 @@ packages:
engines: {node: '>= 12.0.0'}
cpu: [arm64]
os: [linux]
- libc: [glibc]
lightningcss-linux-arm64-musl@1.32.0:
resolution: {integrity: sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==}
engines: {node: '>= 12.0.0'}
cpu: [arm64]
os: [linux]
- libc: [musl]
lightningcss-linux-x64-gnu@1.32.0:
resolution: {integrity: sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==}
engines: {node: '>= 12.0.0'}
cpu: [x64]
os: [linux]
- libc: [glibc]
lightningcss-linux-x64-musl@1.32.0:
resolution: {integrity: sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==}
engines: {node: '>= 12.0.0'}
cpu: [x64]
os: [linux]
- libc: [musl]
lightningcss-win32-arm64-msvc@1.32.0:
resolution: {integrity: sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==}
@@ -4492,8 +4446,8 @@ packages:
resolution: {integrity: sha512-WWdIxpyjEn+FhQJQQv9aQAYlHoNVdzIzUySNV1gHUPDSdZJ3yZn7pAAbQcV7B56Mvu881q9FZV+0Vx2xC44VWA==}
engines: {node: ^18.17.0 || >=20.5.0}
- nanoid@3.3.11:
- resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==}
+ nanoid@3.3.12:
+ resolution: {integrity: sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==}
engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
hasBin: true
@@ -4682,8 +4636,8 @@ packages:
resolution: {integrity: sha512-LKWqWJRhstyYo9pGvgor/ivk2w94eSjE3RGVuzLGlr3NmD8bf7RcYGze1mNdEHRP6TRP6rMuDHk5t44hnTRyow==}
engines: {node: '>=14.19.0'}
- postcss@8.5.10:
- resolution: {integrity: sha512-pMMHxBOZKFU6HgAZ4eyGnwXF/EvPGGqUr0MnZ5+99485wwW41kW91A4LOGxSHhgugZmSChL5AlElNdwlNgcnLQ==}
+ postcss@8.5.15:
+ resolution: {integrity: sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==}
engines: {node: ^10 || ^12 || >=14}
preact@10.29.1:
@@ -4818,8 +4772,8 @@ packages:
react: ^18.0.0 || ^19.0.0
react-dom: ^18.0.0 || ^19.0.0
- react-router@7.14.2:
- resolution: {integrity: sha512-yCqNne6I8IB6rVCH7XUvlBK7/QKyqypBFGv+8dj4QBFJiiRX+FG7/nkdAvGElyvVZ/HQP5N19wzteuTARXi5Gw==}
+ react-router@7.17.0:
+ resolution: {integrity: sha512-FDELK7rTMlCHO5+reyXsPlmfr7N1F91lPHsWYfMEGQm/KQ+F4JFM8jGoeQDmDvdTs93Fw9aSilH+uKRb4/jXvQ==}
engines: {node: '>=20.0.0'}
peerDependencies:
react: '>=18'
@@ -5496,8 +5450,8 @@ packages:
resolution: {integrity: sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==}
engines: {node: '>=18'}
- ws@8.20.0:
- resolution: {integrity: sha512-sAt8BhgNbzCtgGbt2OxmpuryO63ZoDk/sqaB/znQm94T4fCEsy/yV+7CdC1kJhOU9lboAEU7R3kquuycDoibVA==}
+ ws@8.20.1:
+ resolution: {integrity: sha512-It4dO0K5v//JtTXuPkfEOaI3uUN87iYPnqo/ZzqCoG3g8uhA66QUMs/SrM0YK7/NAu+r4LMh/9dq2A7k+rHs+w==}
engines: {node: '>=10.0.0'}
peerDependencies:
bufferutil: ^4.0.1
@@ -5604,7 +5558,7 @@ snapshots:
graphql: 16.13.2
typescript: 5.9.3
- '@apollo/client@3.13.8(@types/react@19.2.14)(graphql-ws@6.0.8(graphql@16.13.2)(ws@8.20.0))(graphql@16.13.2)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)':
+ '@apollo/client@3.13.8(@types/react@19.2.14)(graphql-ws@6.0.8(graphql@16.13.2)(ws@8.20.1))(graphql@16.13.2)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)':
dependencies:
'@graphql-typed-document-node/core': 3.2.0(graphql@16.13.2)
'@wry/caches': 1.0.1
@@ -5621,7 +5575,7 @@ snapshots:
tslib: 2.8.1
zen-observable-ts: 1.2.5
optionalDependencies:
- graphql-ws: 6.0.8(graphql@16.13.2)(ws@8.20.0)
+ graphql-ws: 6.0.8(graphql@16.13.2)(ws@8.20.1)
react: 19.2.5
react-dom: 19.2.5(react@19.2.5)
transitivePeerDependencies:
@@ -6215,9 +6169,9 @@ snapshots:
graphql: 16.13.2
typescript: 5.9.3
- '@graphiql/plugin-doc-explorer@0.4.1(@graphiql/react@0.37.3(@types/node@25.6.0)(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(graphql-ws@6.0.8(graphql@16.13.2)(ws@8.20.0))(graphql@16.13.2)(immer@11.1.4)(react-compiler-runtime@19.1.0-rc.1(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(use-sync-external-store@1.6.0(react@19.2.5)))(@types/react@19.2.14)(graphql@16.13.2)(immer@11.1.4)(react-compiler-runtime@19.1.0-rc.1(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(use-sync-external-store@1.6.0(react@19.2.5))':
+ '@graphiql/plugin-doc-explorer@0.4.1(@graphiql/react@0.37.3(@types/node@25.6.0)(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(graphql-ws@6.0.8(graphql@16.13.2)(ws@8.20.1))(graphql@16.13.2)(immer@11.1.4)(react-compiler-runtime@19.1.0-rc.1(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(use-sync-external-store@1.6.0(react@19.2.5)))(@types/react@19.2.14)(graphql@16.13.2)(immer@11.1.4)(react-compiler-runtime@19.1.0-rc.1(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(use-sync-external-store@1.6.0(react@19.2.5))':
dependencies:
- '@graphiql/react': 0.37.3(@types/node@25.6.0)(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(graphql-ws@6.0.8(graphql@16.13.2)(ws@8.20.0))(graphql@16.13.2)(immer@11.1.4)(react-compiler-runtime@19.1.0-rc.1(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(use-sync-external-store@1.6.0(react@19.2.5))
+ '@graphiql/react': 0.37.3(@types/node@25.6.0)(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(graphql-ws@6.0.8(graphql@16.13.2)(ws@8.20.1))(graphql@16.13.2)(immer@11.1.4)(react-compiler-runtime@19.1.0-rc.1(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(use-sync-external-store@1.6.0(react@19.2.5))
'@headlessui/react': 2.2.10(react-dom@19.2.5(react@19.2.5))(react@19.2.5)
graphql: 16.13.2
react: 19.2.5
@@ -6229,18 +6183,18 @@ snapshots:
- immer
- use-sync-external-store
- '@graphiql/plugin-explorer@5.1.1(@graphiql/react@0.37.3(@types/node@25.6.0)(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(graphql-ws@6.0.8(graphql@16.13.2)(ws@8.20.0))(graphql@16.13.2)(immer@11.1.4)(react-compiler-runtime@19.1.0-rc.1(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(use-sync-external-store@1.6.0(react@19.2.5)))(graphql@16.13.2)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)':
+ '@graphiql/plugin-explorer@5.1.1(@graphiql/react@0.37.3(@types/node@25.6.0)(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(graphql-ws@6.0.8(graphql@16.13.2)(ws@8.20.1))(graphql@16.13.2)(immer@11.1.4)(react-compiler-runtime@19.1.0-rc.1(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(use-sync-external-store@1.6.0(react@19.2.5)))(graphql@16.13.2)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)':
dependencies:
- '@graphiql/react': 0.37.3(@types/node@25.6.0)(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(graphql-ws@6.0.8(graphql@16.13.2)(ws@8.20.0))(graphql@16.13.2)(immer@11.1.4)(react-compiler-runtime@19.1.0-rc.1(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(use-sync-external-store@1.6.0(react@19.2.5))
+ '@graphiql/react': 0.37.3(@types/node@25.6.0)(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(graphql-ws@6.0.8(graphql@16.13.2)(ws@8.20.1))(graphql@16.13.2)(immer@11.1.4)(react-compiler-runtime@19.1.0-rc.1(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(use-sync-external-store@1.6.0(react@19.2.5))
graphiql-explorer: 0.9.0(graphql@16.13.2)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)
graphql: 16.13.2
react: 19.2.5
react-dom: 19.2.5(react@19.2.5)
- '@graphiql/plugin-history@0.4.1(@graphiql/react@0.37.3(@types/node@25.6.0)(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(graphql-ws@6.0.8(graphql@16.13.2)(ws@8.20.0))(graphql@16.13.2)(immer@11.1.4)(react-compiler-runtime@19.1.0-rc.1(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(use-sync-external-store@1.6.0(react@19.2.5)))(@types/node@25.6.0)(@types/react@19.2.14)(graphql-ws@6.0.8(graphql@16.13.2)(ws@8.20.0))(graphql@16.13.2)(immer@11.1.4)(react-compiler-runtime@19.1.0-rc.1(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(use-sync-external-store@1.6.0(react@19.2.5))':
+ '@graphiql/plugin-history@0.4.1(@graphiql/react@0.37.3(@types/node@25.6.0)(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(graphql-ws@6.0.8(graphql@16.13.2)(ws@8.20.1))(graphql@16.13.2)(immer@11.1.4)(react-compiler-runtime@19.1.0-rc.1(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(use-sync-external-store@1.6.0(react@19.2.5)))(@types/node@25.6.0)(@types/react@19.2.14)(graphql-ws@6.0.8(graphql@16.13.2)(ws@8.20.1))(graphql@16.13.2)(immer@11.1.4)(react-compiler-runtime@19.1.0-rc.1(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(use-sync-external-store@1.6.0(react@19.2.5))':
dependencies:
- '@graphiql/react': 0.37.3(@types/node@25.6.0)(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(graphql-ws@6.0.8(graphql@16.13.2)(ws@8.20.0))(graphql@16.13.2)(immer@11.1.4)(react-compiler-runtime@19.1.0-rc.1(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(use-sync-external-store@1.6.0(react@19.2.5))
- '@graphiql/toolkit': 0.11.3(@types/node@25.6.0)(graphql-ws@6.0.8(graphql@16.13.2)(ws@8.20.0))(graphql@16.13.2)
+ '@graphiql/react': 0.37.3(@types/node@25.6.0)(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(graphql-ws@6.0.8(graphql@16.13.2)(ws@8.20.1))(graphql@16.13.2)(immer@11.1.4)(react-compiler-runtime@19.1.0-rc.1(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(use-sync-external-store@1.6.0(react@19.2.5))
+ '@graphiql/toolkit': 0.11.3(@types/node@25.6.0)(graphql-ws@6.0.8(graphql@16.13.2)(ws@8.20.1))(graphql@16.13.2)
react: 19.2.5
react-compiler-runtime: 19.1.0-rc.1(react@19.2.5)
react-dom: 19.2.5(react@19.2.5)
@@ -6253,9 +6207,9 @@ snapshots:
- immer
- use-sync-external-store
- '@graphiql/react@0.37.3(@types/node@25.6.0)(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(graphql-ws@6.0.8(graphql@16.13.2)(ws@8.20.0))(graphql@16.13.2)(immer@11.1.4)(react-compiler-runtime@19.1.0-rc.1(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(use-sync-external-store@1.6.0(react@19.2.5))':
+ '@graphiql/react@0.37.3(@types/node@25.6.0)(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(graphql-ws@6.0.8(graphql@16.13.2)(ws@8.20.1))(graphql@16.13.2)(immer@11.1.4)(react-compiler-runtime@19.1.0-rc.1(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(use-sync-external-store@1.6.0(react@19.2.5))':
dependencies:
- '@graphiql/toolkit': 0.11.3(@types/node@25.6.0)(graphql-ws@6.0.8(graphql@16.13.2)(ws@8.20.0))(graphql@16.13.2)
+ '@graphiql/toolkit': 0.11.3(@types/node@25.6.0)(graphql-ws@6.0.8(graphql@16.13.2)(ws@8.20.1))(graphql@16.13.2)
'@radix-ui/react-dialog': 1.1.15(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)
'@radix-ui/react-dropdown-menu': 2.1.16(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)
'@radix-ui/react-tooltip': 1.2.8(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)
@@ -6284,13 +6238,13 @@ snapshots:
- immer
- use-sync-external-store
- '@graphiql/toolkit@0.11.3(@types/node@25.6.0)(graphql-ws@6.0.8(graphql@16.13.2)(ws@8.20.0))(graphql@16.13.2)':
+ '@graphiql/toolkit@0.11.3(@types/node@25.6.0)(graphql-ws@6.0.8(graphql@16.13.2)(ws@8.20.1))(graphql@16.13.2)':
dependencies:
'@n1ru4l/push-pull-async-iterable-iterator': 3.2.0
graphql: 16.13.2
meros: 1.3.2(@types/node@25.6.0)
optionalDependencies:
- graphql-ws: 6.0.8(graphql@16.13.2)(ws@8.20.0)
+ graphql-ws: 6.0.8(graphql@16.13.2)(ws@8.20.1)
transitivePeerDependencies:
- '@types/node'
@@ -6499,10 +6453,10 @@ snapshots:
'@graphql-tools/utils': 11.0.1(graphql@16.13.2)
'@whatwg-node/disposablestack': 0.0.6
graphql: 16.13.2
- graphql-ws: 6.0.8(graphql@16.13.2)(ws@8.20.0)
- isows: 1.0.7(ws@8.20.0)
+ graphql-ws: 6.0.8(graphql@16.13.2)(ws@8.20.1)
+ isows: 1.0.7(ws@8.20.1)
tslib: 2.8.1
- ws: 8.20.0
+ ws: 8.20.1
transitivePeerDependencies:
- '@fastify/websocket'
- bufferutil
@@ -6529,9 +6483,9 @@ snapshots:
'@graphql-tools/utils': 11.0.1(graphql@16.13.2)
'@types/ws': 8.18.1
graphql: 16.13.2
- isomorphic-ws: 5.0.0(ws@8.20.0)
+ isomorphic-ws: 5.0.0(ws@8.20.1)
tslib: 2.8.1
- ws: 8.20.0
+ ws: 8.20.1
transitivePeerDependencies:
- bufferutil
- utf-8-validate
@@ -6653,10 +6607,10 @@ snapshots:
'@whatwg-node/fetch': 0.10.13
'@whatwg-node/promise-helpers': 1.3.2
graphql: 16.13.2
- isomorphic-ws: 5.0.0(ws@8.20.0)
+ isomorphic-ws: 5.0.0(ws@8.20.1)
sync-fetch: 0.6.0
tslib: 2.8.1
- ws: 8.20.0
+ ws: 8.20.1
transitivePeerDependencies:
- '@fastify/websocket'
- '@types/node'
@@ -7914,9 +7868,9 @@ snapshots:
tslib: 2.8.1
optional: true
- '@types/apollo-upload-client@18.0.1(@types/react@19.2.14)(graphql-ws@6.0.8(graphql@16.13.2)(ws@8.20.0))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)':
+ '@types/apollo-upload-client@18.0.1(@types/react@19.2.14)(graphql-ws@6.0.8(graphql@16.13.2)(ws@8.20.1))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)':
dependencies:
- '@apollo/client': 3.13.8(@types/react@19.2.14)(graphql-ws@6.0.8(graphql@16.13.2)(ws@8.20.0))(graphql@16.13.2)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)
+ '@apollo/client': 3.13.8(@types/react@19.2.14)(graphql-ws@6.0.8(graphql@16.13.2)(ws@8.20.1))(graphql@16.13.2)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)
'@types/extract-files': 13.0.2
graphql: 16.13.2
transitivePeerDependencies:
@@ -8267,7 +8221,7 @@ snapshots:
sirv: 3.0.2
tinyrainbow: 3.1.0
vitest: 4.1.5(@types/node@25.6.0)(@vitest/browser-playwright@4.1.5)(@vitest/coverage-v8@4.1.5)(vite@8.0.9(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(yaml@2.8.3))
- ws: 8.20.0
+ ws: 8.20.1
transitivePeerDependencies:
- bufferutil
- msw
@@ -8422,9 +8376,9 @@ snapshots:
normalize-path: 3.0.0
picomatch: 2.3.2
- apollo-upload-client@18.0.1(@apollo/client@3.13.8(@types/react@19.2.14)(graphql-ws@6.0.8(graphql@16.13.2)(ws@8.20.0))(graphql@16.13.2)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(graphql@16.13.2):
+ apollo-upload-client@18.0.1(@apollo/client@3.13.8(@types/react@19.2.14)(graphql-ws@6.0.8(graphql@16.13.2)(ws@8.20.1))(graphql@16.13.2)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(graphql@16.13.2):
dependencies:
- '@apollo/client': 3.13.8(@types/react@19.2.14)(graphql-ws@6.0.8(graphql@16.13.2)(ws@8.20.0))(graphql@16.13.2)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)
+ '@apollo/client': 3.13.8(@types/react@19.2.14)(graphql-ws@6.0.8(graphql@16.13.2)(ws@8.20.1))(graphql@16.13.2)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)
extract-files: 13.0.0
graphql: 16.13.2
@@ -9073,11 +9027,11 @@ snapshots:
react: 19.2.5
react-dom: 19.2.5(react@19.2.5)
- graphiql@5.2.2(@types/node@25.6.0)(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(graphql-ws@6.0.8(graphql@16.13.2)(ws@8.20.0))(graphql@16.13.2)(immer@11.1.4)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(use-sync-external-store@1.6.0(react@19.2.5)):
+ graphiql@5.2.2(@types/node@25.6.0)(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(graphql-ws@6.0.8(graphql@16.13.2)(ws@8.20.1))(graphql@16.13.2)(immer@11.1.4)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(use-sync-external-store@1.6.0(react@19.2.5)):
dependencies:
- '@graphiql/plugin-doc-explorer': 0.4.1(@graphiql/react@0.37.3(@types/node@25.6.0)(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(graphql-ws@6.0.8(graphql@16.13.2)(ws@8.20.0))(graphql@16.13.2)(immer@11.1.4)(react-compiler-runtime@19.1.0-rc.1(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(use-sync-external-store@1.6.0(react@19.2.5)))(@types/react@19.2.14)(graphql@16.13.2)(immer@11.1.4)(react-compiler-runtime@19.1.0-rc.1(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(use-sync-external-store@1.6.0(react@19.2.5))
- '@graphiql/plugin-history': 0.4.1(@graphiql/react@0.37.3(@types/node@25.6.0)(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(graphql-ws@6.0.8(graphql@16.13.2)(ws@8.20.0))(graphql@16.13.2)(immer@11.1.4)(react-compiler-runtime@19.1.0-rc.1(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(use-sync-external-store@1.6.0(react@19.2.5)))(@types/node@25.6.0)(@types/react@19.2.14)(graphql-ws@6.0.8(graphql@16.13.2)(ws@8.20.0))(graphql@16.13.2)(immer@11.1.4)(react-compiler-runtime@19.1.0-rc.1(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(use-sync-external-store@1.6.0(react@19.2.5))
- '@graphiql/react': 0.37.3(@types/node@25.6.0)(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(graphql-ws@6.0.8(graphql@16.13.2)(ws@8.20.0))(graphql@16.13.2)(immer@11.1.4)(react-compiler-runtime@19.1.0-rc.1(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(use-sync-external-store@1.6.0(react@19.2.5))
+ '@graphiql/plugin-doc-explorer': 0.4.1(@graphiql/react@0.37.3(@types/node@25.6.0)(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(graphql-ws@6.0.8(graphql@16.13.2)(ws@8.20.1))(graphql@16.13.2)(immer@11.1.4)(react-compiler-runtime@19.1.0-rc.1(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(use-sync-external-store@1.6.0(react@19.2.5)))(@types/react@19.2.14)(graphql@16.13.2)(immer@11.1.4)(react-compiler-runtime@19.1.0-rc.1(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(use-sync-external-store@1.6.0(react@19.2.5))
+ '@graphiql/plugin-history': 0.4.1(@graphiql/react@0.37.3(@types/node@25.6.0)(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(graphql-ws@6.0.8(graphql@16.13.2)(ws@8.20.1))(graphql@16.13.2)(immer@11.1.4)(react-compiler-runtime@19.1.0-rc.1(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(use-sync-external-store@1.6.0(react@19.2.5)))(@types/node@25.6.0)(@types/react@19.2.14)(graphql-ws@6.0.8(graphql@16.13.2)(ws@8.20.1))(graphql@16.13.2)(immer@11.1.4)(react-compiler-runtime@19.1.0-rc.1(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(use-sync-external-store@1.6.0(react@19.2.5))
+ '@graphiql/react': 0.37.3(@types/node@25.6.0)(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(graphql-ws@6.0.8(graphql@16.13.2)(ws@8.20.1))(graphql@16.13.2)(immer@11.1.4)(react-compiler-runtime@19.1.0-rc.1(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(use-sync-external-store@1.6.0(react@19.2.5))
graphql: 16.13.2
react: 19.2.5
react-compiler-runtime: 19.1.0-rc.1(react@19.2.5)
@@ -9125,11 +9079,11 @@ snapshots:
graphql: 16.13.2
tslib: 2.8.1
- graphql-ws@6.0.8(graphql@16.13.2)(ws@8.20.0):
+ graphql-ws@6.0.8(graphql@16.13.2)(ws@8.20.1):
dependencies:
graphql: 16.13.2
optionalDependencies:
- ws: 8.20.0
+ ws: 8.20.1
graphql@16.13.2: {}
@@ -9321,13 +9275,13 @@ snapshots:
isobject@3.0.1: {}
- isomorphic-ws@5.0.0(ws@8.20.0):
+ isomorphic-ws@5.0.0(ws@8.20.1):
dependencies:
- ws: 8.20.0
+ ws: 8.20.1
- isows@1.0.7(ws@8.20.0):
+ isows@1.0.7(ws@8.20.1):
dependencies:
- ws: 8.20.0
+ ws: 8.20.1
istanbul-lib-coverage@3.2.2: {}
@@ -9956,7 +9910,7 @@ snapshots:
mute-stream@2.0.0: {}
- nanoid@3.3.11: {}
+ nanoid@3.3.12: {}
no-case@3.0.4:
dependencies:
@@ -9981,12 +9935,12 @@ snapshots:
nullthrows@1.1.1: {}
- nuqs@2.8.9(react-router@7.14.2(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(react@19.2.5):
+ nuqs@2.8.9(react-router@7.17.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(react@19.2.5):
dependencies:
'@standard-schema/spec': 1.0.0
react: 19.2.5
optionalDependencies:
- react-router: 7.14.2(react-dom@19.2.5(react@19.2.5))(react@19.2.5)
+ react-router: 7.17.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5)
nypm@0.6.5:
dependencies:
@@ -10175,9 +10129,9 @@ snapshots:
pngjs@7.0.0: {}
- postcss@8.5.10:
+ postcss@8.5.15:
dependencies:
- nanoid: 3.3.11
+ nanoid: 3.3.12
picocolors: 1.1.1
source-map-js: 1.2.1
@@ -10330,7 +10284,7 @@ snapshots:
react: 19.2.5
react-dom: 19.2.5(react@19.2.5)
- react-router@7.14.2(react-dom@19.2.5(react@19.2.5))(react@19.2.5):
+ react-router@7.17.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5):
dependencies:
cookie: 1.1.1
react: 19.2.5
@@ -10950,7 +10904,7 @@ snapshots:
dependencies:
lightningcss: 1.32.0
picomatch: 4.0.4
- postcss: 8.5.10
+ postcss: 8.5.15
rolldown: 1.0.0-rc.16
tinyglobby: 0.2.16
optionalDependencies:
@@ -11042,7 +10996,7 @@ snapshots:
string-width: 7.2.0
strip-ansi: 7.2.0
- ws@8.20.0: {}
+ ws@8.20.1: {}
y18n@5.0.8: {}
diff --git a/frontend/app/src/entities/artifacts/ui/artifact-header.tsx b/frontend/app/src/entities/artifacts/ui/artifact-header.tsx
index 80ae31370c6..85600ab225c 100644
--- a/frontend/app/src/entities/artifacts/ui/artifact-header.tsx
+++ b/frontend/app/src/entities/artifacts/ui/artifact-header.tsx
@@ -3,6 +3,7 @@ import { ArtifactDetailsMenu } from "@/entities/artifacts/ui/artifact-details-me
import { ArtifactGenerateButton } from "@/entities/artifacts/ui/artifact-generate-button";
import { ArtifactStatusBadge } from "@/entities/artifacts/ui/artifact-status-badge";
import { NodeMetadataPopover } from "@/entities/nodes/object/ui/object-details/node-metadata-popover";
+import { RefreshButton } from "@/entities/nodes/object/ui/object-details/refresh-button";
import { getNodeLabel } from "@/entities/nodes/object/utils/get-node-label";
interface ArtifactHeaderProps {
@@ -17,6 +18,8 @@ export function ArtifactHeader({ artifact }: ArtifactHeaderProps) {