diff --git a/.github/workflows/repo-contract.yml b/.github/workflows/repo-contract.yml new file mode 100644 index 0000000..1993119 --- /dev/null +++ b/.github/workflows/repo-contract.yml @@ -0,0 +1,19 @@ +name: repo-contract + +# utils owns the shared gate, so it runs its own copy directly (no shim, no +# second checkout). Other repos use repo_contract/templates/repo-contract.yml. +on: + push: + branches: [main] + pull_request: + workflow_dispatch: + +jobs: + repo-contract: + name: repo contract + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Enforce the repo contract + run: bash scripts/check_repo_contract.sh diff --git a/.github/workflows/shared-gates-tests.yml b/.github/workflows/shared-gates-tests.yml index f68330d..d0c8cd0 100644 --- a/.github/workflows/shared-gates-tests.yml +++ b/.github/workflows/shared-gates-tests.yml @@ -48,3 +48,17 @@ jobs: --cov=file_length --cov-branch \ --cov-report=term-missing \ --cov-fail-under=100 + + - name: repo_contract — 100% branch coverage on the gate itself + run: | + PYTHONPATH=. python -m pytest repo_contract/tests -q \ + --cov=repo_contract --cov-branch \ + --cov-report=term-missing \ + --cov-fail-under=100 + + - name: coverage_gaps — 100% branch coverage on the lister + run: | + PYTHONPATH=. python -m pytest coverage_gaps/tests -q \ + --cov=coverage_gaps --cov-branch \ + --cov-report=term-missing \ + --cov-fail-under=100 diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index f3ad392..9c47312 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -43,6 +43,13 @@ repos: language: system pass_filenames: true + - id: repo-contract + name: repo contract (CLAUDE.md Commands + test_changed) + entry: scripts/check_repo_contract.sh + language: system + pass_filenames: false + files: ^(CLAUDE\.md|AGENTS\.md|scripts/test_changed\.sh)$ + - id: md-naming name: markdown naming convention entry: scripts/check_md_naming.sh diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..c5d5af3 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,17 @@ +# utils + +Monorepo of shared gates and libraries (dep_freshness, file_length, md_naming, +repo_contract, coverage_gaps, crdt-sync, gatelock, freedays, music_theory, ...). +Python packages with a root-level `/tests` run with `PYTHONPATH=.`; +subprojects with their own `pyproject.toml` run via `scripts/run_subproject_tests.sh `. +`scripts/check_repo_contract.sh` is the REAL shared gate (not a shim): never +overwrite it with `repo_contract/templates/check_repo_contract.shim.sh`. + +## Commands + +- run: n/a: collection of gates and libraries, no single entry point +- test: `PYTHONPATH=. python -m pytest -q coverage_gaps/tests dep_freshness/tests file_length/tests home_paths/tests md_naming/tests repo_contract/tests` +- test-changed: `scripts/test_changed.sh` +- lint: `ruff check coverage_gaps repo_contract` +- coverage: `PYTHONPATH=. python -m pytest -q repo_contract/tests --cov=repo_contract --cov-branch --cov-report=xml --cov-fail-under=100` +- coverage-gaps: `coverage-gaps coverage.xml` diff --git a/coverage_gaps/__init__.py b/coverage_gaps/__init__.py new file mode 100644 index 0000000..11b0fbf --- /dev/null +++ b/coverage_gaps/__init__.py @@ -0,0 +1,7 @@ +"""Deterministic coverage-gap lister: a coverage report in, a task list out. + +Replaces "spawn an LLM agent to explore where coverage is missing". Reads lcov, +Cobertura XML, JaCoCo XML or coverage.py JSON and lists, per file, exactly +which lines and branches are uncovered. Exit codes are the verdict (0 covered, +1 gaps, 2 unreadable) so CI and hooks adjudicate without a model. +""" diff --git a/coverage_gaps/__main__.py b/coverage_gaps/__main__.py new file mode 100644 index 0000000..9916058 --- /dev/null +++ b/coverage_gaps/__main__.py @@ -0,0 +1,5 @@ +"""`python3 -m coverage_gaps` -- the entrypoint the `coverage-gaps` wrapper calls.""" + +from coverage_gaps.cli import main + +raise SystemExit(main()) diff --git a/coverage_gaps/bin/coverage-gaps b/coverage_gaps/bin/coverage-gaps new file mode 100755 index 0000000..9aff39d --- /dev/null +++ b/coverage_gaps/bin/coverage-gaps @@ -0,0 +1,16 @@ +#!/bin/bash + +# ============================================================================ +# coverage-gaps -- per-file task list of uncovered lines/branches. +# Thin wrapper over `python3 -m coverage_gaps` (see coverage_gaps/cli.py). +# Usage: coverage-gaps [--json] [--max-files N] [--min-missed N] +# [--path-prefix P] Exit: 0 covered | 1 gaps | 2 bad input +# ============================================================================ + +set -euo pipefail + +BIN_DIR="$(cd "$(dirname "$(readlink -f "${BASH_SOURCE[0]}")")" && pwd -P)" +readonly BIN_DIR + +PYTHONPATH="$BIN_DIR/../.." python3 -m coverage_gaps "$@" +exit $? diff --git a/coverage_gaps/cli.py b/coverage_gaps/cli.py new file mode 100644 index 0000000..d6ba5ba --- /dev/null +++ b/coverage_gaps/cli.py @@ -0,0 +1,63 @@ +"""Command line: `coverage-gaps REPORT [--json] [--max-files N] ...`.""" + +from __future__ import annotations + +import argparse +import sys +from collections.abc import Sequence +from pathlib import Path + +from coverage_gaps.models import ReportError +from coverage_gaps.parsers import load_report +from coverage_gaps.render import select, to_json, to_markdown + +EXIT_COVERED = 0 +EXIT_GAPS = 1 +EXIT_UNREADABLE = 2 + + +def build_parser() -> argparse.ArgumentParser: + """Argument parser (format is auto-detected from file content).""" + parser = argparse.ArgumentParser( + prog="coverage-gaps", + description="List uncovered lines/branches from lcov, Cobertura, JaCoCo " + "or coverage.py JSON reports. Exit 0 covered, 1 gaps, 2 unreadable.", + ) + parser.add_argument("report", type=Path, help="coverage report file") + parser.add_argument("--json", action="store_true", help="emit JSON, not markdown") + parser.add_argument( + "--max-files", + type=int, + default=0, + metavar="N", + help="list only the N worst files (0 = all)", + ) + parser.add_argument( + "--min-missed", + type=int, + default=1, + metavar="N", + help="skip files with fewer than N missed lines+branches", + ) + parser.add_argument( + "--path-prefix", + default="", + metavar="PREFIX", + help="only files whose path starts with PREFIX", + ) + return parser + + +def main(argv: Sequence[str] | None = None) -> int: + """Run the tool and return the process exit code.""" + args = build_parser().parse_args(argv) + try: + fmt, gaps = load_report(args.report) + except ReportError as exc: + print(f"coverage-gaps: {exc}", file=sys.stderr) + return EXIT_UNREADABLE + files = select(gaps, path_prefix=args.path_prefix, min_missed=args.min_missed) + shown = args.max_files if args.max_files > 0 else len(files) + render = to_json if args.json else to_markdown + sys.stdout.write(render(fmt, files, shown) + ("\n" if args.json else "")) + return EXIT_GAPS if files else EXIT_COVERED diff --git a/coverage_gaps/models.py b/coverage_gaps/models.py new file mode 100644 index 0000000..2a4bed3 --- /dev/null +++ b/coverage_gaps/models.py @@ -0,0 +1,69 @@ +"""Format-neutral data model shared by every parser and renderer.""" + +from __future__ import annotations + +from dataclasses import dataclass, field + + +class ReportError(Exception): + """The input is unreadable, unrecognised or malformed (exit code 2).""" + + +@dataclass +class FileAccumulator: + """Mutable per-file collector that merges repeated records for one path. + + Reports may list a file more than once (lcov re-runs, Cobertura inner + classes). A line or branch counts as covered if ANY record covered it. + """ + + lines: dict[int, bool] = field(default_factory=dict) + branches: dict[tuple[int, int, int], bool] = field(default_factory=dict) + + def add_line(self, line: int, covered: bool) -> None: + """Record a line, OR-merging with earlier records of the same line.""" + self.lines[line] = self.lines.get(line, False) or covered + + def add_branch(self, line: int, block: int, index: int, covered: bool) -> None: + """Record one branch outcome, OR-merging on (line, block, index).""" + key = (line, block, index) + self.branches[key] = self.branches.get(key, False) or covered + + +@dataclass(frozen=True) +class FileGap: + """Coverage state of one source file.""" + + path: str + total_lines: int + missed_lines: tuple[int, ...] + total_branches: int + missed_branch_lines: tuple[int, ...] + missed_branches: int + + @property + def missed(self) -> int: + """Missed lines plus missed branches: the sort key.""" + return len(self.missed_lines) + self.missed_branches + + @property + def percent(self) -> float: + """Percent covered over lines and branches combined (100 if empty).""" + total = self.total_lines + self.total_branches + if total == 0: + return 100.0 + return 100.0 * (total - self.missed) / total + + +def build_gap(path: str, acc: FileAccumulator) -> FileGap: + """Freeze an accumulator into a FileGap.""" + missed_lines = tuple(sorted(n for n, ok in acc.lines.items() if not ok)) + missed_br = [key for key, ok in acc.branches.items() if not ok] + return FileGap( + path=path, + total_lines=len(acc.lines), + missed_lines=missed_lines, + total_branches=len(acc.branches), + missed_branch_lines=tuple(sorted({key[0] for key in missed_br})), + missed_branches=len(missed_br), + ) diff --git a/coverage_gaps/parsers.py b/coverage_gaps/parsers.py new file mode 100644 index 0000000..875ed8d --- /dev/null +++ b/coverage_gaps/parsers.py @@ -0,0 +1,132 @@ +"""Parsers for lcov, Cobertura XML, JaCoCo XML and coverage.py JSON.""" + +from __future__ import annotations + +import json +import re +import xml.etree.ElementTree as ET +from collections.abc import Callable +from pathlib import Path + +from coverage_gaps.models import FileAccumulator, FileGap, ReportError, build_gap + +_CONDITION = re.compile(r"\((\d+)/(\d+)\)") + + +def _finish(files: dict[str, FileAccumulator]) -> list[FileGap]: + return [build_gap(path, acc) for path, acc in files.items()] + + +def parse_lcov(text: str) -> list[FileGap]: + """Parse lcov.info: SF / DA / BRDA records, `end_of_record` optional.""" + files: dict[str, FileAccumulator] = {} + acc: FileAccumulator | None = None + for raw in text.splitlines(): + line = raw.strip() + if line.startswith("SF:"): + acc = files.setdefault(line[3:], FileAccumulator()) + elif acc is not None and line.startswith("DA:"): + num, hits, *_ = line[3:].split(",") + acc.add_line(int(num), int(hits) > 0) + elif acc is not None and line.startswith("BRDA:"): + num, block, branch, taken = line[5:].split(",") + covered = taken not in ("-", "0") + acc.add_branch(int(num), int(block), _branch_id(branch), covered) + elif line == "end_of_record": + acc = None + return _finish(files) + + +def _branch_id(branch: str) -> int: + return int(branch) if branch.isdigit() else 0 + + +def parse_cobertura(text: str) -> list[FileGap]: + """Parse Cobertura `coverage.xml` (coverage.py, gcovr, ...).""" + root = ET.fromstring(text) + files: dict[str, FileAccumulator] = {} + for cls in root.iter("class"): + acc = files.setdefault(cls.get("filename", ""), FileAccumulator()) + for node in cls.iter("line"): + num = int(node.get("number", "0")) + acc.add_line(num, int(node.get("hits", "0")) > 0) + match = _CONDITION.search(node.get("condition-coverage", "")) + if match: + hit, total = int(match[1]), int(match[2]) + for idx in range(total): + acc.add_branch(num, 0, idx, idx < hit) + return _finish(files) + + +def parse_jacoco(text: str) -> list[FileGap]: + """Parse JaCoCo XML: per `sourcefile` line records (mi/ci/mb/cb).""" + root = ET.fromstring(text) + files: dict[str, FileAccumulator] = {} + for package in root.iter("package"): + base = package.get("name", "") + for source in package.iter("sourcefile"): + name = source.get("name", "") + acc = files.setdefault( + f"{base}/{name}" if base else name, FileAccumulator() + ) + for node in source.iter("line"): + num = int(node.get("nr", "0")) + acc.add_line(num, int(node.get("ci", "0")) > 0) + missed_b, covered_b = int(node.get("mb", "0")), int(node.get("cb", "0")) + for idx in range(missed_b + covered_b): + acc.add_branch(num, 0, idx, idx >= missed_b) + return _finish(files) + + +def parse_coverage_json(text: str) -> list[FileGap]: + """Parse `coverage json` output (with or without branch data).""" + files: dict[str, FileAccumulator] = {} + for path, data in json.loads(text).get("files", {}).items(): + acc = files.setdefault(path, FileAccumulator()) + for num in data.get("executed_lines", []): + acc.add_line(num, True) + for num in data.get("missing_lines", []): + acc.add_line(num, False) + missing = data.get("missing_branches", []) + for idx, (src, _dst) in enumerate(missing): + acc.add_branch(abs(src), 1, idx, False) + covered = data.get("summary", {}).get("covered_branches", 0) + for idx in range(covered): + acc.add_branch(0, 2, idx, True) + return _finish(files) + + +def detect_format(text: str) -> str: + """Return 'lcov' | 'cobertura' | 'jacoco' | 'json' from file content.""" + head = text.lstrip()[:4000] + if head.startswith("{"): + return "json" + if head.startswith("<"): + if " tuple[str, list[FileGap]]: + """Read and parse a report; raise ReportError on any failure.""" + try: + text = path.read_text(encoding="utf-8", errors="replace") + except OSError as exc: + raise ReportError(f"cannot read {path}: {exc.strerror}") from exc + fmt = detect_format(text) + try: + return fmt, _PARSERS[fmt](text) + except (ET.ParseError, ValueError, TypeError, AttributeError) as exc: + raise ReportError(f"malformed {fmt} report {path}: {exc}") from exc diff --git a/coverage_gaps/render.py b/coverage_gaps/render.py new file mode 100644 index 0000000..ed300ca --- /dev/null +++ b/coverage_gaps/render.py @@ -0,0 +1,102 @@ +"""Filtering and rendering: markdown (agent task list) and JSON.""" + +from __future__ import annotations + +import json +from collections.abc import Iterable, Sequence + +from coverage_gaps.models import FileGap + + +def compact_ranges(lines: Iterable[int]) -> str: + """Collapse line numbers into '12-18, 40'.""" + nums = sorted(set(lines)) + parts: list[str] = [] + idx = 0 + while idx < len(nums): + end = idx + while end + 1 < len(nums) and nums[end + 1] == nums[end] + 1: + end += 1 + parts.append(_span(nums[idx], nums[end])) + idx = end + 1 + return ", ".join(parts) + + +def _span(start: int, end: int) -> str: + return str(start) if start == end else f"{start}-{end}" + + +def select( + gaps: Sequence[FileGap], *, path_prefix: str = "", min_missed: int = 1 +) -> list[FileGap]: + """Keep files with gaps under the prefix, worst first (ties by path).""" + kept = [ + gap + for gap in gaps + if gap.path.startswith(path_prefix) and gap.missed >= max(min_missed, 1) + ] + return sorted(kept, key=lambda gap: (-gap.missed, gap.path)) + + +def _totals(files: Sequence[FileGap]) -> dict[str, int]: + return { + "files": len(files), + "missed_lines": sum(len(gap.missed_lines) for gap in files), + "missed_branches": sum(gap.missed_branches for gap in files), + } + + +def to_json(fmt: str, files: Sequence[FileGap], shown: int) -> str: + """Machine-readable output; `files` is already sorted and filtered.""" + entries = [ + { + "path": gap.path, + "percent_covered": round(gap.percent, 2), + "missed": gap.missed, + "missed_lines": compact_ranges(gap.missed_lines), + "missed_line_numbers": list(gap.missed_lines), + "missed_branch_count": gap.missed_branches, + "missed_branch_lines": list(gap.missed_branch_lines), + } + for gap in files[:shown] + ] + doc = { + "format": fmt, + "total": _totals(files), + "shown": len(entries), + "files": entries, + } + return json.dumps(doc, indent=2) + + +def to_markdown(fmt: str, files: Sequence[FileGap], shown: int) -> str: + """Markdown task list meant to be pasted to an agent verbatim.""" + total = _totals(files) + out = [ + "# Coverage gaps", + "", + ( + f"Format: {fmt}. Total: {total['files']} files with gaps, " + f"{total['missed_lines']} missed lines, " + f"{total['missed_branches']} missed branches." + ), + ] + if shown < len(files): + out.append(f"Showing the {shown} worst files.") + if not files: + return "\n".join(out) + "\n\nNo coverage gaps.\n" + out += ["", "Write tests that cover every item below, then re-run the report.", ""] + for rank, gap in enumerate(files[:shown], start=1): + out.append(f"## {rank}. `{gap.path}` - {gap.percent:.1f}% covered") + if gap.missed_lines: + out.append( + f"- missed lines ({len(gap.missed_lines)}): " + f"{compact_ranges(gap.missed_lines)}" + ) + if gap.missed_branches: + out.append( + f"- missed branches ({gap.missed_branches}) at lines: " + f"{compact_ranges(gap.missed_branch_lines)}" + ) + out.append("") + return "\n".join(out).rstrip() + "\n" diff --git a/coverage_gaps/tests/__init__.py b/coverage_gaps/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/coverage_gaps/tests/conftest.py b/coverage_gaps/tests/conftest.py new file mode 100644 index 0000000..a0d8bb8 --- /dev/null +++ b/coverage_gaps/tests/conftest.py @@ -0,0 +1,89 @@ +"""Small fixture reports, one per supported format, all describing a.py/b.py. + +a.py: lines 1-5 with 2,3 and 5 missed + one missed branch on line 4. +b.py: fully covered (must never be listed). +""" + +from __future__ import annotations + +import json +import sys +from pathlib import Path + +import pytest + +sys.path.insert(0, str(Path(__file__).resolve().parents[2])) + +LCOV = """TN: +SF:a.py +DA:1,1 +DA:2,0 +DA:3,0 +DA:4,1 +DA:5,0 +BRDA:4,0,0,1 +BRDA:4,0,1,- +end_of_record +SF:b.py +DA:1,3 +end_of_record +SF:a.py +DA:1,1 +DA:2,0 +end_of_record +""" + +COBERTURA = """ + + + + + + + + +""" + +JACOCO = """ + + + + + + + + +""" + +COVERAGE_JSON = json.dumps( + { + "files": { + "a.py": { + "executed_lines": [1, 4], + "missing_lines": [2, 3, 5], + "missing_branches": [[4, 5]], + "summary": {"covered_branches": 1}, + }, + "b.py": { + "executed_lines": [1], + "missing_lines": [], + "missing_branches": [], + }, + } + } +) + + +@pytest.fixture +def reports(tmp_path: Path) -> dict[str, Path]: + """Write each fixture report to disk, keyed by format name.""" + out = {} + for name, body in { + "lcov": LCOV, + "cobertura": COBERTURA, + "jacoco": JACOCO, + "json": COVERAGE_JSON, + }.items(): + out[name] = tmp_path / f"{name}.rpt" + out[name].write_text(body, encoding="utf-8") + return out diff --git a/coverage_gaps/tests/test_parsers.py b/coverage_gaps/tests/test_parsers.py new file mode 100644 index 0000000..845fcca --- /dev/null +++ b/coverage_gaps/tests/test_parsers.py @@ -0,0 +1,73 @@ +"""Parser and format-detection tests.""" + +from __future__ import annotations + +from pathlib import Path + +import pytest + +from coverage_gaps.models import FileGap, ReportError +from coverage_gaps.parsers import detect_format, load_report + + +def _by_path(gaps: list[FileGap]) -> dict[str, FileGap]: + return {gap.path: gap for gap in gaps} + + +@pytest.mark.parametrize("fmt", ["lcov", "cobertura", "json"]) +def test_a_py_gaps_match_across_formats(reports: dict[str, Path], fmt: str) -> None: + detected, gaps = load_report(reports[fmt]) + assert detected == fmt + a = _by_path(gaps)["a.py"] + assert a.missed_lines == (2, 3, 5) + assert a.missed_branch_lines == (4,) + assert a.missed_branches == 1 + assert a.total_lines == 5 + assert a.total_branches == 2 + assert _by_path(gaps)["b.py"].missed == 0 + + +def test_jacoco(reports: dict[str, Path]) -> None: + fmt, gaps = load_report(reports["jacoco"]) + assert fmt == "jacoco" + by = _by_path(gaps) + assert by["com/x/A.kt"].missed_lines == (2, 3, 5) + assert by["com/x/A.kt"].missed_branch_lines == (4,) + assert by["com/x/A.kt"].total_branches == 2 + assert "Top.kt" in by + + +def test_percent_and_empty_file() -> None: + gap = FileGap("x", 4, (1,), 4, (2,), 1) + assert gap.percent == 75.0 + assert FileGap("y", 0, (), 0, (), 0).percent == 100.0 + + +def test_lcov_ignores_records_outside_a_file_and_dash_branch_ids() -> None: + from coverage_gaps.parsers import parse_lcov + + gaps = parse_lcov( + "DA:1,0\nBRDA:1,0,0,0\nSF:z\nBRDA:2,0,-,0\nend_of_record\nDA:9,0\n" + ) + assert len(gaps) == 1 + assert gaps[0].missed_lines == () + assert gaps[0].missed_branch_lines == (2,) + + +def test_detect_unknown_and_xml_without_known_root() -> None: + for text in ("hello", "", ""): + with pytest.raises(ReportError): + detect_format(text) + + +def test_load_errors(tmp_path: Path) -> None: + with pytest.raises(ReportError, match="cannot read"): + load_report(tmp_path / "missing") + bad_xml = tmp_path / "bad.xml" + bad_xml.write_text(" None: + assert compact_ranges([]) == "" + assert compact_ranges([40, 12, 13, 14, 15, 16, 17, 18, 18]) == "12-18, 40" + assert compact_ranges([1, 3, 5, 6]) == "1, 3, 5-6" + + +def test_select_sorts_and_filters() -> None: + small = FileGap("src/b.py", 5, (1,), 0, (), 0) + big = FileGap("lib/a.py", 5, (1, 2), 2, (3,), 1) + clean = FileGap("src/c.py", 5, (), 0, (), 0) + assert [g.path for g in select([small, clean, big])] == ["lib/a.py", "src/b.py"] + assert [g.path for g in select([small, big], path_prefix="src")] == ["src/b.py"] + assert [g.path for g in select([small, big], min_missed=2)] == ["lib/a.py"] + + +def test_markdown_branch_only_gap() -> None: + out = to_markdown("lcov", [FileGap("z.py", 2, (), 2, (7,), 1)], 1) + assert "- missed lines" not in out + assert "missed branches (1) at lines: 7" in out + + +def test_module_entrypoint( + reports: dict[str, Path], monkeypatch: pytest.MonkeyPatch +) -> None: + import runpy + + monkeypatch.setattr("sys.argv", ["coverage_gaps", str(reports["lcov"])]) + with pytest.raises(SystemExit) as exc: + runpy.run_module("coverage_gaps", run_name="__main__") + assert exc.value.code == 1 + + +def test_markdown_no_gaps() -> None: + assert "No coverage gaps." in to_markdown("lcov", [], 0) + + +@pytest.mark.parametrize("fmt", ["lcov", "cobertura", "jacoco", "json"]) +def test_cli_markdown_exit_1( + reports: dict[str, Path], fmt: str, capsys: pytest.CaptureFixture[str] +) -> None: + assert main([str(reports[fmt])]) == 1 + out = capsys.readouterr().out + assert "2-3, 5" in out + assert "missed branches (1) at lines: 4" in out + assert "b.py" not in out.replace("A.kt", "") + + +def test_cli_json_and_max_files( + reports: dict[str, Path], capsys: pytest.CaptureFixture[str] +) -> None: + assert main([str(reports["jacoco"]), "--json", "--max-files", "1"]) == 1 + doc = json.loads(capsys.readouterr().out) + assert doc["shown"] == 1 + assert doc["total"] == {"files": 1, "missed_lines": 3, "missed_branches": 1} + assert doc["files"][0]["missed_lines"] == "2-3, 5" + + +def test_cli_markdown_truncation_note( + reports: dict[str, Path], capsys: pytest.CaptureFixture[str], tmp_path: Path +) -> None: + two = tmp_path / "two.info" + two.write_text("SF:x\nDA:1,0\nend_of_record\nSF:y\nDA:1,0\nDA:2,0\nend_of_record\n") + assert main([str(two), "--max-files", "1"]) == 1 + out = capsys.readouterr().out + assert "Showing the 1 worst files." in out + assert "`y`" in out + assert "`x`" not in out + + +def test_cli_exit_0_when_filtered_clean( + reports: dict[str, Path], capsys: pytest.CaptureFixture[str] +) -> None: + assert main([str(reports["lcov"]), "--path-prefix", "b."]) == 0 + assert main([str(reports["lcov"]), "--json", "--min-missed", "99"]) == 0 + assert "No coverage gaps." in capsys.readouterr().out + + +def test_cli_exit_2(tmp_path: Path, capsys: pytest.CaptureFixture[str]) -> None: + assert main([str(tmp_path / "nope")]) == 2 + junk = tmp_path / "junk" + junk.write_text("junk") + assert main([str(junk)]) == 2 + assert "coverage-gaps:" in capsys.readouterr().err diff --git a/dependency-freshness.allowlist.yaml b/dependency-freshness.allowlist.yaml index 849ae7e..f508f28 100644 --- a/dependency-freshness.allowlist.yaml +++ b/dependency-freshness.allowlist.yaml @@ -27,7 +27,7 @@ exceptions: pinned: "6.0.3" latest_known: "7.0.2" reason: >- - typescript-eslint 8.70.0 -- itself the latest release -- declares + typescript-eslint 8.71.0 -- itself the latest release -- declares `typescript: ">=4.8.4 <6.1.0"` and throws "typescript-eslint does not support TS 7.0" when eslint loads its config. No 9.x exists. Taking TS 7 would mean dropping every type-aware lint rule from every repo that pins @@ -36,21 +36,4 @@ exceptions: awesome-mcp-explorer, europe-county-map and every other repo pinning typescript, which is exactly why the entry lives here and not in eight separate files. - blocked_by: "transitive:typescript-eslint@8.70.0" - - - ecosystem: npm - package: typescript-eslint - pinned: "8.70.0" - latest_known: "8.70.1" - reason: >- - The meta-package published 8.70.1, but its own dependency - @typescript-eslint/scope-manager has not: `pnpm install --force` - resolves the tree and fails with ERR_PNPM_NO_MATCHING_VERSION, - because typescript-eslint@8.70.1 requires - @typescript-eslint/scope-manager@8.70.1 and the npm registry's - latest published scope-manager is still 8.70.0. Not installable at - any pin, so 8.70.0 -- the last version whose whole dependency tree - actually resolves -- is what every repo pinning it stays on. - Held fleet-wide for the same reason the typescript entry above is: - it blocks every repo pinning typescript-eslint, not just this one. - blocked_by: "transitive:@typescript-eslint/scope-manager@8.70.0" + blocked_by: "transitive:typescript-eslint@8.71.0" diff --git a/freedays/requirements.txt b/freedays/requirements.txt index 0a8cd49..8702f9f 100644 --- a/freedays/requirements.txt +++ b/freedays/requirements.txt @@ -2,9 +2,9 @@ # floor is satisfied by anything newer, so it tells a checker nothing # about whether this repo is actually current. bandit[toml]==1.9.4 -mypy==2.3.1 +mypy==2.4.0 pre-commit==4.6.2 -pylint==4.0.9 +pylint==4.1.2 pytest==9.1.1 pytest-cov==7.1.0 -ruff==0.16.9 +ruff==0.16.10 diff --git a/gatelock/requirements.txt b/gatelock/requirements.txt index 0a8cd49..8702f9f 100644 --- a/gatelock/requirements.txt +++ b/gatelock/requirements.txt @@ -2,9 +2,9 @@ # floor is satisfied by anything newer, so it tells a checker nothing # about whether this repo is actually current. bandit[toml]==1.9.4 -mypy==2.3.1 +mypy==2.4.0 pre-commit==4.6.2 -pylint==4.0.9 +pylint==4.1.2 pytest==9.1.1 pytest-cov==7.1.0 -ruff==0.16.9 +ruff==0.16.10 diff --git a/music_theory/requirements.txt b/music_theory/requirements.txt index b72d027..316f82b 100644 --- a/music_theory/requirements.txt +++ b/music_theory/requirements.txt @@ -2,10 +2,10 @@ # floor is satisfied by anything newer, so it tells a checker nothing # about whether this repo is actually current. bandit[toml]==1.9.4 -mypy==2.3.1 +mypy==2.4.0 numpy==2.5.3 pre-commit==4.6.2 -pylint==4.0.9 +pylint==4.1.2 pytest==9.1.1 pytest-cov==7.1.0 -ruff==0.16.9 +ruff==0.16.10 diff --git a/repo_contract/README.md b/repo_contract/README.md new file mode 100644 index 0000000..0d7959f --- /dev/null +++ b/repo_contract/README.md @@ -0,0 +1,66 @@ +# repo_contract + +Shared gate: every repo tells an agent how to run and test it, so nobody has to +explore. Enforced like the other shared gates (real gate in `~/src/utils`, a +thin shim per repo, pre-commit + CI). + +## The contract + +`CLAUDE.md` (or `AGENTS.md` that `CLAUDE.md` symlinks to) has a `## Commands` +section with one line per key, exactly `- : \`\``: + +```markdown +## Commands + +- run: `` | n/a: library +- test: `` +- test-changed: `scripts/test_changed.sh` +- lint: `` +- coverage: `` +- coverage-gaps: `coverage-gaps ` +``` + +Rules (all machine-checked): + +1. All six keys present, once each. A value is a backticked command or + `n/a: ` / `n/a ()`. A bare `n/a` fails: n/a carries a reason. +2. `test-changed` invokes an existing, executable `scripts/test_changed.sh`-style + script that runs only tests related to files changed vs HEAD, falls back to + the full suite when it cannot map, and prints failures plus a one-line + summary only. +3. `coverage-gaps` runs the shared lister `coverage-gaps ` + (`~/src/utils/coverage_gaps`). +4. `test`, `test-changed`, `lint`, `coverage` are quiet: no `-v`/`--verbose`/ + expanded reporter, in the command or in the script's runner lines. +5. No placeholders (``, `TODO`, `...`). +6. If `test` is n/a, then `test-changed`, `coverage`, `coverage-gaps` are n/a + too, and the repo must contain no tests (bounded scan). + +## Use + +```bash +python3 ~/src/utils/repo_contract/check.py --repo # 0 pass / 1 violations / 2 unreadable +python3 ~/src/utils/repo_contract/bootstrap.py --repo # print proposal +python3 ~/src/utils/repo_contract/bootstrap.py --repo --write +python3 ~/src/utils/repo_contract/sweep.py # ~/data/claude-scratch/repo-contract/SWEEP.md +``` + +Bootstrap never overwrites: it appends the section (or inserts missing lines at +the end of an existing `## Commands`), creates `CLAUDE.md -> AGENTS.md` when +only AGENTS.md exists, and writes `scripts/test_changed.sh` only if absent. +Sources, in priority order: existing CLAUDE.md, `scripts/.sh`, Makefile, +package.json (node only), CI workflows, stack defaults. Anything it cannot infer +is written as ``, which the gate rejects until a human fills it. + +## Adding the gate to a repo + +1. Copy `templates/check_repo_contract.shim.sh` to `scripts/check_repo_contract.sh` + (never copy `scripts/check_repo_contract.sh` from this repo). +2. Add `templates/pre-commit-snippet.yaml` to `.pre-commit-config.yaml`. +3. Copy `templates/repo-contract.yml` to `.github/workflows/`. + +## Tests + +```bash +PYTHONPATH=. python -m pytest repo_contract/tests -q --cov=repo_contract --cov-branch --cov-fail-under=100 +``` diff --git a/repo_contract/__init__.py b/repo_contract/__init__.py new file mode 100644 index 0000000..3f9ea4b --- /dev/null +++ b/repo_contract/__init__.py @@ -0,0 +1 @@ +"""Repo contract: every repo documents exact run/test/lint/coverage commands.""" diff --git a/repo_contract/bootstrap.py b/repo_contract/bootstrap.py new file mode 100644 index 0000000..ddaa18b --- /dev/null +++ b/repo_contract/bootstrap.py @@ -0,0 +1,123 @@ +"""Propose (or with --write, create) the `## Commands` section and test script. + +Usage: bootstrap.py --repo PATH [--write]. Never overwrites: the section is +appended (or missing lines inserted at the end of an existing section) and +scripts/test_changed.sh is only created when absent. +""" + +from __future__ import annotations + +import argparse +import shutil +import sys +from pathlib import Path + +if __package__ in (None, ""): # pragma: no cover - plain-script import path + sys.path.insert(0, str(Path(__file__).resolve().parent.parent)) + +from repo_contract.contract import HEADING, has_section, parse_entries +from repo_contract.infer import TEST_CHANGED, Proposal, propose +from repo_contract.stacks import detect_stacks + +TEMPLATES = Path(__file__).resolve().parent / "templates" + + +def insert_lines(text: str, new: list[str]) -> str: + """`text` with `new` lines added: appended section, or end of existing one.""" + if not new: + return text + if not has_section(text): + sep = ( + "" + if not text or text.endswith("\n\n") + else ("\n" if text.endswith("\n") else "\n\n") + ) + return f"{text}{sep}{HEADING}\n\n" + "\n".join(new) + "\n" + lines = text.splitlines() + start = next(i for i, ln in enumerate(lines) if ln.strip() == HEADING) + end = next( + (i for i in range(start + 1, len(lines)) if lines[i].startswith("## ")), + len(lines), + ) + while end > start + 1 and not lines[end - 1].strip(): + end -= 1 + lines[end:end] = new + return "\n".join(lines) + "\n" + + +def doc_path(repo: Path) -> Path: + """The file to edit: CLAUDE.md (through a symlink), else AGENTS.md, else new CLAUDE.md.""" + claude, agents = repo / "CLAUDE.md", repo / "AGENTS.md" + if claude.exists(): + return claude.resolve() + return agents if agents.is_file() else claude + + +def missing_lines(prop: Proposal, text: str) -> list[str]: + """Proposal lines for keys the existing document does not yet define.""" + present, _ = parse_entries(text) + return [ln for ln in prop.lines() if ln.split(":", 1)[0][2:] not in present] + + +def script_plan(repo: Path, prop: Proposal) -> Path | None: + """The template to copy for scripts/test_changed.sh, or None if not needed.""" + wants = prop.values.get("test-changed", "") == f"`{TEST_CHANGED}`" + if not wants or (repo / TEST_CHANGED).exists(): + return None + template = TEMPLATES / f"{prop.stack}.sh" + return template if template.is_file() else None + + +def apply( + repo: Path, doc: Path, text: str, new: list[str], script: Path | None +) -> list[str]: + """Write the changes; returns human-readable actions taken.""" + done: list[str] = [] + if not (repo / "CLAUDE.md").exists() and (repo / "AGENTS.md").is_file(): + (repo / "CLAUDE.md").symlink_to("AGENTS.md") + done.append("symlinked CLAUDE.md -> AGENTS.md") + if new: + doc.write_text(insert_lines(text, new), encoding="utf-8") + done.append(f"wrote {len(new)} line(s) to {doc.name}") + if script is not None: + target = repo / TEST_CHANGED + target.parent.mkdir(parents=True, exist_ok=True) + shutil.copyfile(script, target) + target.chmod(0o755) + done.append(f"created {TEST_CHANGED} from {script.name}") + return done + + +def main(argv: list[str] | None = None) -> int: + """CLI entry point; 0 on success, 2 for a path that is not a git repo.""" + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--repo", type=Path, required=True) + parser.add_argument("--write", action="store_true") + args = parser.parse_args(argv) + repo = args.repo.expanduser().resolve() + if not repo.is_dir() or not (repo / ".git").exists(): + print(f"bootstrap: {repo} is not a git repo", file=sys.stderr) + return 2 + stacks = detect_stacks(repo) + prop = propose(repo, stacks) + doc = doc_path(repo) + text = doc.read_text(encoding="utf-8") if doc.is_file() else "" + new = missing_lines(prop, text) + script = script_plan(repo, prop) + print(f"# {repo.name} (stack: {', '.join(stacks) or 'unknown'})") + print("\n".join(new) if new else "(Commands section already complete)") + print( + f"# script: {TEST_CHANGED} <- {script.name}" + if script + else "# script: none to create" + ) + for note in prop.notes: + print(f"# note: {note}") + if args.write: + for action in apply(repo, doc, text, new, script): + print(f"# {action}") + return 0 + + +if __name__ == "__main__": # pragma: no cover + sys.exit(main()) diff --git a/repo_contract/check.py b/repo_contract/check.py new file mode 100644 index 0000000..80217fc --- /dev/null +++ b/repo_contract/check.py @@ -0,0 +1,142 @@ +"""Gate: the repo documents exact run/test/lint/coverage commands. + +Usage: check.py [--repo PATH]. Exit 0 pass, 1 violations (printed), 2 not a +repo or unreadable CLAUDE.md. Contract text lives in README.md / DOCS-*.md. +""" + +from __future__ import annotations + +import argparse +import os +import sys +from pathlib import Path + +if __package__ in (None, ""): # pragma: no cover - plain-script import path + sys.path.insert(0, str(Path(__file__).resolve().parent.parent)) + +from repo_contract.contract import ( + KEYS, + LISTER, + Entry, + entry_problems, + has_section, + parse_entries, + script_token, + verbose_in_script, +) +from repo_contract.stacks import detect_stacks, has_tests + +EXIT_OK, EXIT_VIOLATIONS, EXIT_UNREADABLE = 0, 1, 2 +DOC_NAMES = ("CLAUDE.md", "AGENTS.md") + + +class Unreadable(Exception): + """Raised for a path that is not a repo or a doc that cannot be read.""" + + +def read_doc(repo: Path) -> str | None: + """CLAUDE.md text (symlinks followed); None if absent; Unreadable on I/O error.""" + doc = repo / "CLAUDE.md" + if not doc.exists(): + return None + try: + return doc.read_text(encoding="utf-8") + except (OSError, UnicodeDecodeError) as exc: + raise Unreadable(f"{doc}: {exc}") from exc + + +def _missing_doc(repo: Path) -> list[str]: + """Violation for a missing CLAUDE.md, hinting at the AGENTS.md symlink.""" + if (repo / "CLAUDE.md").is_symlink(): + return ["CLAUDE.md is a dangling symlink"] + if (repo / "AGENTS.md").is_file(): + return [ + "CLAUDE.md missing: symlink it to AGENTS.md (ln -s AGENTS.md CLAUDE.md)" + ] + return ["CLAUDE.md missing (or AGENTS.md that CLAUDE.md symlinks to)"] + + +def _check_changed(repo: Path, command: str) -> list[str]: + """test-changed must run an existing, quiet entry-point script.""" + script = script_token(command, repo) + if script is None: + msg = "'- test-changed:' must invoke an existing changed-files script" + return [f"{msg} (e.g. scripts/test_changed.sh)"] + out: list[str] = [] + if not os.access(script, os.X_OK): + out.append(f"{script.relative_to(repo)} is not executable") + try: + if verbose_in_script(script.read_text(encoding="utf-8")): + out.append(f"{script.relative_to(repo)} runs a test tool verbosely") + except (OSError, UnicodeDecodeError) as exc: + raise Unreadable(f"{script}: {exc}") from exc + return out + + +def _check_consistency( + repo: Path, entries: dict[str, Entry], stacks: list[str] +) -> list[str]: + """Cross-entry rules: n/a vs real tests, coverage-gaps lister.""" + out: list[str] = [] + test = entries["test"] + if test.command is None: + if has_tests(repo, stacks): + out.append("'- test:' is n/a but the repo contains tests") + for key in ("test-changed", "coverage", "coverage-gaps"): + if entries[key].command is not None: + out.append(f"'- {key}:' must be n/a when test is n/a") + return out + if entries["test-changed"].command is None: + out.append("'- test-changed:' cannot be n/a while test is a real command") + cov, gaps = entries["coverage"], entries["coverage-gaps"] + if cov.command is not None and gaps.command is None: + out.append( + "'- coverage-gaps:' must run the shared lister: `coverage-gaps `" + ) + if gaps.command is not None and gaps.command.split()[0] != LISTER: + out.append(f"'- coverage-gaps:' must start with `{LISTER} `") + return out + + +def violations(repo: Path) -> tuple[list[str], list[str]]: + """(violations, detected stacks) for `repo`. Raises Unreadable.""" + stacks = detect_stacks(repo) + text = read_doc(repo) + if text is None: + return _missing_doc(repo), stacks + if not has_section(text): + return ["CLAUDE.md has no '## Commands' section"], stacks + entries, out = parse_entries(text) + absent = [k for k in KEYS if k not in entries] + if absent: + out.append("'## Commands' is missing: " + ", ".join(f"- {k}:" for k in absent)) + return out, stacks + for entry in entries.values(): + out.extend(entry_problems(entry)) + changed = entries["test-changed"] + if changed.command is not None: + out.extend(_check_changed(repo, changed.command)) + out.extend(_check_consistency(repo, entries, stacks)) + return out, stacks + + +def main(argv: list[str] | None = None) -> int: + """CLI entry point; returns the process exit code.""" + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--repo", type=Path, default=Path.cwd()) + repo = parser.parse_args(argv).repo.expanduser().resolve() + if not repo.is_dir() or not (repo / ".git").exists(): + print(f"repo-contract: {repo} is not a git repo", file=sys.stderr) + return EXIT_UNREADABLE + try: + found, _ = violations(repo) + except Unreadable as exc: + print(f"repo-contract: unreadable: {exc}", file=sys.stderr) + return EXIT_UNREADABLE + for problem in found: + print(f"repo-contract: {repo.name}: {problem}", file=sys.stderr) + return EXIT_VIOLATIONS if found else EXIT_OK + + +if __name__ == "__main__": # pragma: no cover + sys.exit(main()) diff --git a/repo_contract/contract.py b/repo_contract/contract.py new file mode 100644 index 0000000..150c506 --- /dev/null +++ b/repo_contract/contract.py @@ -0,0 +1,134 @@ +"""The contract text: keys, the `## Commands` parser, entry validation.""" + +from __future__ import annotations + +import re +import shlex +from dataclasses import dataclass +from pathlib import Path + +KEYS: tuple[str, ...] = ( + "run", + "test", + "test-changed", + "lint", + "coverage", + "coverage-gaps", +) +QUIET_KEYS = frozenset({"test", "test-changed", "lint", "coverage"}) +HEADING = "## Commands" +LISTER = "coverage-gaps" + +_ENTRY = re.compile(r"^[-*]\s+([a-z-]+):\s*(.*?)\s*$") +_CMD = re.compile(r"^`([^`]+)`") +_NA = re.compile(r"^n/a\s*(?::\s*(.*)|\(\s*(.*?)\s*\))\s*$") +_PLACEHOLDER = re.compile(r"<[A-Za-z][A-Za-z _-]*>|\bFILL IN\b|\bTODO\b|\.\.\.") +_VERBOSE = re.compile(r"^(-v+|--verbose|--reporter=expanded)$") +RUNNER_LINE = re.compile( + r"pytest|flutter|jest|vitest|go test|cargo|gradlew|bats|npm|pnpm|yarn" +) + + +@dataclass(frozen=True) +class Entry: + """One `- key: value` line: a command, or an n/a with its reason.""" + + key: str + command: str | None + na_reason: str | None + + +def commands_section(text: str) -> list[str]: + """Lines of the `## Commands` section (empty if absent); fences respected.""" + lines: list[str] = [] + inside = fenced = False + for line in text.splitlines(): + if line.lstrip().startswith("```"): + fenced = not fenced + elif not fenced and line.startswith("## "): + inside = line.strip() == HEADING + continue + if inside: + lines.append(line) + return lines + + +def has_section(text: str) -> bool: + """Whether a `## Commands` heading exists outside code fences.""" + fenced = False + for line in text.splitlines(): + if line.lstrip().startswith("```"): + fenced = not fenced + elif not fenced and line.strip() == HEADING: + return True + return False + + +def parse_entries(text: str) -> tuple[dict[str, Entry], list[str]]: + """Entries by key plus syntax problems (duplicates, n/a without reason).""" + entries: dict[str, Entry] = {} + problems: list[str] = [] + for line in commands_section(text): + match = _ENTRY.match(line) + if not match or match.group(1) not in KEYS: + continue + key, value = match.group(1), match.group(2) + if key in entries: + problems.append(f"duplicate '- {key}:' entry") + continue + cmd, na = _CMD.match(value), _NA.match(value) + if cmd: + entries[key] = Entry(key, cmd.group(1).strip(), None) + elif na and (na.group(1) or na.group(2) or "").strip(): + entries[key] = Entry(key, None, (na.group(1) or na.group(2)).strip()) + elif value.startswith("n/a"): + problems.append(f"'- {key}:' n/a must carry a reason: 'n/a: '") + else: + problems.append(f"'- {key}:' needs a backticked command or 'n/a: '") + return entries, problems + + +def is_verbose(command: str) -> bool: + """True if the command carries a verbose flag (-v, --verbose, expanded).""" + try: + tokens = shlex.split(command) + except ValueError: + tokens = command.split() + return any(_VERBOSE.match(t) for t in tokens) or "--reporter expanded" in command + + +def verbose_in_script(text: str) -> bool: + """Verbose flag on a runner invocation line of a shell script.""" + return any( + is_verbose(line) + for line in text.splitlines() + if not line.lstrip().startswith("#") and RUNNER_LINE.search(line) + ) + + +def entry_problems(entry: Entry) -> list[str]: + """Problems with a single command entry (placeholder, verbosity).""" + if entry.command is None: + return [] + out: list[str] = [] + if _PLACEHOLDER.search(entry.command): + out.append(f"'- {entry.key}:' still holds a placeholder: {entry.command}") + if entry.key in QUIET_KEYS and is_verbose(entry.command): + out.append( + f"'- {entry.key}:' must be quiet by default (verbose flag in: {entry.command})" + ) + return out + + +def script_token(command: str, repo: Path) -> Path | None: + """The first token of `command` that names an existing file in `repo`.""" + try: + tokens = shlex.split(command) + except ValueError: + tokens = command.split() + for token in tokens: + if "/" in token or token.endswith((".sh", ".py")): + candidate = repo / token + if candidate.is_file(): + return candidate + return None diff --git a/repo_contract/infer.py b/repo_contract/infer.py new file mode 100644 index 0000000..cccbec9 --- /dev/null +++ b/repo_contract/infer.py @@ -0,0 +1,241 @@ +"""Infer a proposed `## Commands` section from a repo's existing config.""" + +from __future__ import annotations + +import re +from dataclasses import dataclass, field +from pathlib import Path + +from repo_contract.contract import KEYS, LISTER, is_verbose +from repo_contract.sources import ( + MAKE_TARGETS, + NPM_SCRIPTS, + ci_commands, + doc_commands, + first_match, + make_targets, + package_scripts, + read_text, +) +from repo_contract.stacks import has_tests + +TEST_CHANGED = "scripts/test_changed.sh" +NO_TESTS = "n/a: no automated tests yet" +NO_COVERAGE = "n/a: no coverage tooling for this stack" +REPORTS = { + "python": "coverage.xml", + "flutter": "coverage/lcov.info", + "node": "coverage/lcov.info", + "go": "coverage.out", + "rust": "lcov.info", + "gradle": "build/reports/jacoco/test/jacocoTestReport.xml", +} +_VERBOSE_TOKEN = re.compile(r"\s(-v+|--verbose)(?=\s|$)") + + +@dataclass +class Proposal: + """Contract values by key (already formatted) plus notes for the human.""" + + stack: str + values: dict[str, str] = field(default_factory=dict) + notes: list[str] = field(default_factory=list) + + def lines(self) -> list[str]: + """The `- key: value` lines in contract order.""" + return [f"- {k}: {self.values[k]}" for k in KEYS if k in self.values] + + +def quiet(command: str) -> str: + """Strip verbose flags and add the tool's quiet default where known.""" + command = _VERBOSE_TOKEN.sub("", command) + if "pytest" in command and not re.search(r"\s-q+\b", command): + command = command.replace("pytest", "pytest -q", 1) + if ( + "flutter test" in command + and "--reporter" not in command + and " -r " not in command + ): + command += " --reporter=compact" + return ( + command + if not is_verbose(command) + else command.replace("--reporter=expanded", "") + ) + + +def _prefix(repo: Path) -> str: + """How python tools are launched in this repo.""" + if (repo / "uv.lock").is_file(): + return "uv run" + return "poetry run" if (repo / "poetry.lock").is_file() else "python3 -m" + + +def _pm(repo: Path) -> str: + """Node package manager from the lockfile.""" + if (repo / "pnpm-lock.yaml").is_file(): + return "pnpm" + return "yarn" if (repo / "yarn.lock").is_file() else "npm" + + +def stack_defaults(repo: Path, stack: str) -> dict[str, str]: + """Conventional commands for the stack (lowest-priority source).""" + cfg = read_text(repo / "pyproject.toml") + read_text( + repo / ".pre-commit-config.yaml" + ) + py = _prefix(repo) + pm = _pm(repo) + ruff = "ruff check ." if py == "python3 -m" else f"{py} ruff check ." + table: dict[str, dict[str, str]] = { + "python": { + "test": f"{py} pytest -q", + "lint": ruff if "ruff" in cfg else "pre-commit run --all-files", + "coverage": f"{py} pytest -q --cov --cov-branch --cov-report=xml", + }, + "flutter": { + "run": "flutter run", + "test": "flutter test --reporter=compact", + "lint": "flutter analyze", + "coverage": "flutter test --reporter=compact --coverage", + }, + "node": { + "test": f"{pm} test --silent", + "lint": f"{pm} run lint", + "coverage": f"{pm} test --silent --coverage", + }, + "go": { + "test": "go test ./...", + "lint": "go vet ./...", + "coverage": "go test -coverprofile=coverage.out ./...", + }, + "rust": { + "test": "cargo test --quiet", + "lint": "cargo clippy --quiet", + "coverage": "cargo llvm-cov --lcov --output-path lcov.info", + }, + "gradle": { + "test": "./gradlew test --quiet", + "lint": "./gradlew lint --quiet", + "coverage": "./gradlew jacocoTestReport --quiet", + }, + "godot": _godot_defaults(repo), + "shell": {"lint": "shellcheck scripts/*.sh", "coverage": NO_COVERAGE}, + } + return table.get(stack, {}) + + +def _godot_defaults(repo: Path) -> dict[str, str]: + """Godot commands; the test runner comes from addons/ (GUT or gdUnit4).""" + out = {"run": "godot --path .", "coverage": NO_COVERAGE} + if (repo / "addons/gut/gut_cmdln.gd").is_file(): + out["test"] = "godot --headless -s addons/gut/gut_cmdln.gd -gexit -glog=0" + elif (repo / "addons/gdUnit4").is_dir(): + out["test"] = ( + "godot --headless -s -d addons/gdUnit4/bin/GdUnitCmdTool.gd -a res://tests" + ) + return out + + +def _run_default(repo: Path, stack: str) -> str: + """Best guess for `run`, or an n/a with reason.""" + if stack == "python": + for name in ("main.py", "__main__.py", "app.py"): + if (repo / name).is_file(): + return f"python3 {name}" + return "n/a: library (no entry point found)" + if stack == "go": + return ( + "go run ." if (repo / "main.go").is_file() else "n/a: library (no main.go)" + ) + if stack == "rust": + return ( + "cargo run" + if (repo / "src" / "main.rs").is_file() + else "n/a: library (no src/main.rs)" + ) + if stack == "gradle": + return ( + "./gradlew installDebug" + if (repo / "app").is_dir() + else "n/a: library (no app module)" + ) + if stack == "node": + return "n/a: library (no start/dev script)" + return "n/a: collection of scripts, no single entry point" + + +def _pick( + repo: Path, key: str, stack: str, doc: list[str], ci: list[str] +) -> tuple[str | None, str]: + """(command, source) for one key by priority doc > Makefile > npm > CI > default.""" + found = first_match(key, doc) + if found: + return found, "CLAUDE.md" + targets = make_targets(repo) + for name in MAKE_TARGETS.get(key, ()): + if name in targets: + return f"make {name}", "Makefile" + helper = repo / "scripts" / f"{key}.sh" + if helper.is_file(): + return f"scripts/{key}.sh", "scripts/" + scripts = package_scripts(repo) if stack == "node" else {} + for name in NPM_SCRIPTS.get(key, ()): + if name in scripts: + return f"{_pm(repo)} run {name}", "package.json" + found = first_match(key, ci) + if found: + return found, "CI workflow" + return stack_defaults(repo, stack).get(key), "stack default" + + +def propose(repo: Path, stacks: list[str]) -> Proposal: + """Build the proposal for the repo's primary stack.""" + stack = stacks[0] if stacks else "unknown" + prop = Proposal(stack) + doc = doc_commands(read_text(repo / "CLAUDE.md")) + ci = ci_commands(repo) + tested = has_tests(repo, stacks) + for key in ("run", "lint"): + cmd, src = _pick(repo, key, stack, doc, ci) + prop.values[key] = _fmt( + cmd or (_run_default(repo, stack) if key == "run" else None), prop, key, src + ) + if not tested and stack in {"godot", "shell", "unknown"}: + for key in ("test", "test-changed", "coverage", "coverage-gaps"): + prop.values[key] = NO_TESTS + prop.notes.append("no tests detected: test chain proposed as n/a") + return _order(prop) + test, src = _pick(repo, "test", stack, doc, ci) + prop.values["test"] = _fmt(test, prop, "test", src) + prop.values["test-changed"] = f"`{TEST_CHANGED}`" + cov, src = _pick(repo, "coverage", stack, doc, ci) + prop.values["coverage"] = _fmt(cov, prop, "coverage", src) + report = REPORTS.get(stack) + if prop.values["coverage"].startswith("n/a"): + prop.values["coverage-gaps"] = NO_COVERAGE + elif report: + prop.values["coverage-gaps"] = f"`{LISTER} {report}`" + else: + prop.values["coverage-gaps"] = "``" + prop.notes.append("coverage-gaps: unknown report path, fill in") + if not tested: + prop.notes.append("no tests detected, but the stack normally has them") + return _order(prop) + + +def _fmt(command: str | None, prop: Proposal, key: str, source: str) -> str: + """Format a command (quiet-ified) or a placeholder, noting the source.""" + if not command: + prop.notes.append(f"{key}: could not infer, fill in") + return "``" + if command.startswith("n/a"): + prop.notes.append(f"{key}: {command}") + return command + prop.notes.append(f"{key}: from {source}") + return f"`{quiet(command)}`" + + +def _order(prop: Proposal) -> Proposal: + """Keep values in contract order.""" + prop.values = {k: prop.values[k] for k in KEYS if k in prop.values} + return prop diff --git a/repo_contract/sources.py b/repo_contract/sources.py new file mode 100644 index 0000000..e66eaff --- /dev/null +++ b/repo_contract/sources.py @@ -0,0 +1,87 @@ +"""Where bootstrap reads existing commands from: doc, Makefile, package.json, CI.""" + +from __future__ import annotations + +import json +import re +from pathlib import Path + +PATTERNS: dict[str, re.Pattern[str]] = { + "test": re.compile( + r"pytest|flutter test|vitest|jest|go test|cargo (nextest|test)|gradlew.* test|bats" + ), + "lint": re.compile( + r"ruff|flutter analyze|dart analyze|eslint|golangci|clippy|shellcheck|pre-commit run|ktlint" + ), + "coverage": re.compile(r"--cov|--coverage|llvm-cov|jacoco|coverage run"), +} +MAKE_TARGETS = { + "run": ("run", "start", "dev"), + "test": ("test", "tests", "check"), + "lint": ("lint",), + "coverage": ("coverage", "cov"), +} +NPM_SCRIPTS = { + "run": ("start", "dev"), + "test": ("test",), + "lint": ("lint",), + "coverage": ("coverage", "test:coverage", "test:cov"), +} +_SPAN = re.compile(r"`([^`\n]+)`") +_MAKE = re.compile(r"^([A-Za-z0-9_.-]+):(?!=)", re.MULTILINE) +_CI_RUN = re.compile(r"^\s*(?:-\s*)?run:\s*(\S.*?)\s*$") + + +def read_text(path: Path) -> str: + """File text, or '' when absent/unreadable (sources are best-effort).""" + try: + return path.read_text(encoding="utf-8") + except (OSError, UnicodeDecodeError): + return "" + + +def doc_commands(text: str) -> list[str]: + """Backticked spans of a CLAUDE.md that look like commands (contain a space).""" + return [s for s in _SPAN.findall(text) if " " in s and s[0] not in "/~"] + + +def make_targets(repo: Path) -> set[str]: + """Target names defined in the repo's Makefile.""" + return set(_MAKE.findall(read_text(repo / "Makefile"))) + + +def package_scripts(repo: Path) -> dict[str, str]: + """`scripts` of package.json ({} if absent or malformed).""" + try: + data = json.loads(read_text(repo / "package.json")) + except json.JSONDecodeError: + return {} + scripts = data.get("scripts") if isinstance(data, dict) else None + return scripts if isinstance(scripts, dict) else {} + + +def ci_commands(repo: Path) -> list[str]: + """Single-line `run:` commands from .github/workflows (no expressions).""" + out: list[str] = [] + for wf in sorted((repo / ".github" / "workflows").glob("*.y*ml")): + for line in read_text(wf).splitlines(): + match = _CI_RUN.match(line) + if ( + match + and "${{" not in match.group(1) + and match.group(1) not in {"|", ">", "|-"} + ): + out.append(match.group(1)) + return out + + +def first_match(key: str, commands: list[str]) -> str | None: + """First command matching the key's pattern.""" + pattern = PATTERNS.get(key) + if pattern is None: + return None + for cmd in commands: + covering = PATTERNS["coverage"].search(cmd) is not None + if pattern.search(cmd) and (covering == (key == "coverage")): + return cmd + return None diff --git a/repo_contract/stacks.py b/repo_contract/stacks.py new file mode 100644 index 0000000..0328d13 --- /dev/null +++ b/repo_contract/stacks.py @@ -0,0 +1,118 @@ +"""Stack detection and a bounded "does this repo have tests" probe.""" + +from __future__ import annotations + +import os +from pathlib import Path + +PRUNE = frozenset( + { + ".git", + "node_modules", + "build", + ".dart_tool", + ".venv", + "venv", + "target", + "__pycache__", + ".gradle", + ".idea", + "dist", + ".mypy_cache", + ".ruff_cache", + ".pytest_cache", + "Pods", + ".godot", + "vendor", + "third_party", + } +) +MAX_DEPTH = 4 +MAX_FILES = 5000 + +# stack -> marker files at the repo root (checked in order of this table). +MARKERS: tuple[tuple[str, tuple[str, ...]], ...] = ( + ("flutter", ("pubspec.yaml",)), + ("godot", ("project.godot",)), + ( + "gradle", + ("build.gradle", "build.gradle.kts", "settings.gradle", "settings.gradle.kts"), + ), + ("python", ("pyproject.toml", "setup.py", "setup.cfg", "requirements.txt")), + ("node", ("package.json",)), + ("go", ("go.mod",)), + ("rust", ("Cargo.toml",)), +) + + +def _walk(repo: Path) -> list[Path]: + """Files under `repo` (pruned, depth- and count-bounded; never follows links).""" + found: list[Path] = [] + base = len(repo.parts) + for root, dirs, files in os.walk(repo, followlinks=False): + dirs[:] = [d for d in dirs if d not in PRUNE] + if len(Path(root).parts) - base >= MAX_DEPTH: + dirs[:] = [] + found.extend(Path(root, f) for f in files) + if len(found) >= MAX_FILES: + break + return found + + +def detect_stacks(repo: Path) -> list[str]: + """Stacks present in `repo`, most specific first; `shell` if only scripts.""" + stacks = [s for s, names in MARKERS if any((repo / n).is_file() for n in names)] + if not stacks and any(p.suffix == ".sh" for p in _walk(repo)): + stacks.append("shell") + return stacks + + +def _has_pattern( + files: list[Path], suffixes: tuple[str, ...], names: tuple[str, ...] +) -> bool: + """True when some file ends with a test suffix or sits under a test dir.""" + for path in files: + if path.name.endswith(suffixes) or path.name.startswith(names): + return True + return False + + +def has_tests(repo: Path, stacks: list[str]) -> bool: + """Whether the repo ships any automated tests (best-effort, bounded).""" + files = _walk(repo) + rels = [p.relative_to(repo) for p in files] + in_test_dir = any( + part in {"tests", "test", "spec", "__tests__"} + for r in rels + for part in r.parts[:-1] + ) + named = _has_pattern( + files, + ( + "_test.py", + "_test.dart", + ".test.ts", + ".test.js", + ".test.tsx", + ".spec.ts", + ".spec.js", + "_test.go", + "Test.kt", + "Test.java", + ".bats", + "_test.gd", + ), + ("test_",), + ) + return in_test_dir or named or _node_test_script(repo, stacks) + + +def _node_test_script(repo: Path, stacks: list[str]) -> bool: + """A real `scripts.test` in package.json (not npm's placeholder).""" + if "node" not in stacks: + return False + try: + text = (repo / "package.json").read_text(encoding="utf-8") + except (OSError, UnicodeDecodeError): + return False + return '"test"' in text and "no test specified" not in text diff --git a/repo_contract/sweep.py b/repo_contract/sweep.py new file mode 100644 index 0000000..1754fe6 --- /dev/null +++ b/repo_contract/sweep.py @@ -0,0 +1,102 @@ +"""Run the contract check over every git repo under ~/src and ~ and write SWEEP.md.""" + +from __future__ import annotations + +import sys +from collections import Counter +from dataclasses import dataclass +from pathlib import Path + +if __package__ in (None, ""): # pragma: no cover - plain-script import path + sys.path.insert(0, str(Path(__file__).resolve().parent.parent)) + +from repo_contract.check import Unreadable, violations + +OUT = Path.home() / "data" / "claude-scratch" / "repo-contract" / "SWEEP.md" + + +@dataclass(frozen=True) +class Result: + """Outcome for one repo.""" + + name: str + path: Path + stacks: list[str] + problems: list[str] + error: str | None = None + + @property + def passed(self) -> bool: + """True when there were no problems and no read error.""" + return not self.problems and self.error is None + + +def find_repos(roots: list[Path]) -> list[Path]: + """Direct children of each root that are git repos (`.git` is a directory).""" + found = [ + d + for r in roots + if r.is_dir() + for d in sorted(r.iterdir()) + if not d.name.startswith(".") and (d / ".git").is_dir() + ] + return sorted(found, key=lambda p: str(p)) + + +def run_one(repo: Path) -> Result: + """Check one repo, folding read errors into the result.""" + try: + problems, stacks = violations(repo) + except Unreadable as exc: + return Result(repo.name, repo, [], [], f"unreadable: {exc}") + return Result(repo.name, repo, stacks, problems) + + +def render(results: list[Result]) -> str: + """Markdown report: summary, by-stack counts, per-repo table.""" + passed = sum(r.passed for r in results) + by_stack: Counter[str] = Counter() + fails: Counter[str] = Counter() + for r in results: + key = r.stacks[0] if r.stacks else "none" + by_stack[key] += 1 + fails[key] += not r.passed + out = [ + "# Repo contract sweep", + "", + f"{len(results)} repos: {passed} pass, {len(results) - passed} fail.", + "", + "| primary stack | repos | fail |", + "|---|---|---|", + *[f"| {s} | {n} | {fails[s]} |" for s, n in sorted(by_stack.items())], + "", + "| repo | result | stacks | missing |", + "|---|---|---|---|", + ] + for r in results: + verdict = "PASS" if r.passed else "FAIL" + detail = r.error or "; ".join(r.problems) + out.append( + f"| {r.name} | {verdict} | {', '.join(r.stacks) or '-'} | {detail.replace('|', '/')} |" + ) + return "\n".join(out) + "\n" + + +def main( + argv: list[str] | None = None, roots: list[Path] | None = None, out: Path = OUT +) -> int: + """Sweep, write the report, print the summary line. Always exit 0.""" + del argv + home = Path.home() + results = [run_one(r) for r in find_repos(roots or [home / "src", home])] + out.parent.mkdir(parents=True, exist_ok=True) + out.write_text(render(results), encoding="utf-8") + passed = sum(r.passed for r in results) + print( + f"repo-contract sweep: {len(results)} repos, {passed} pass, {len(results) - passed} fail -> {out}" + ) + return 0 + + +if __name__ == "__main__": # pragma: no cover + sys.exit(main()) diff --git a/repo_contract/templates/check_repo_contract.shim.sh b/repo_contract/templates/check_repo_contract.shim.sh new file mode 100755 index 0000000..8488858 --- /dev/null +++ b/repo_contract/templates/check_repo_contract.shim.sh @@ -0,0 +1,26 @@ +#!/bin/bash + +# ============================================================================ +# Fail if this repo breaks the shared repo contract. +# +# Thin delegate to the shared gate in ~/src/utils, which owns the contract +# text. Copy this file to scripts/check_repo_contract.sh in a repo; never copy +# the shared gate itself, or the shim would exec itself forever. +# ============================================================================ + +set -euo pipefail + +readonly SHARED_GATE="${UTILS_ROOT:-$HOME/src/utils}/scripts/check_repo_contract.sh" + +main() { + if [[ ! -x "$SHARED_GATE" ]]; then + echo "Error: shared repo-contract gate not found at $SHARED_GATE" >&2 + echo " Clone github.com/kuhyx/utils to ~/src/utils, or set" >&2 + echo " UTILS_ROOT to where it lives." >&2 + exit 1 + fi + + exec bash "$SHARED_GATE" "$@" +} + +main "$@" diff --git a/repo_contract/templates/flutter.sh b/repo_contract/templates/flutter.sh new file mode 100755 index 0000000..025e200 --- /dev/null +++ b/repo_contract/templates/flutter.sh @@ -0,0 +1,41 @@ +#!/bin/bash + +# ============================================================================ +# Run only the tests related to files changed vs HEAD (staged, unstaged and +# untracked). Quiet: failures plus a one-line summary. Falls back to the full +# suite when a change cannot be mapped to tests. Generated by repo_contract. +# ============================================================================ + +set -euo pipefail + +cd "$(git rev-parse --show-toplevel)" +CHANGED=() +while IFS= read -r f; do + [[ -n "$f" && -e "$f" ]] && CHANGED+=("$f") +done < <({ git diff --name-only HEAD 2>/dev/null || true; git ls-files --others --exclude-standard; } | sort -u) + +full=0 +tests=() +for f in "${CHANGED[@]}"; do + case "$f" in + pubspec.yaml | pubspec.lock | test/flutter_test_config.dart) full=1 ;; + test/*_test.dart) tests+=("$f") ;; + lib/*.dart) + rel="${f#lib/}" + if [[ -f "test/${rel%.dart}_test.dart" ]]; then + tests+=("test/${rel%.dart}_test.dart") + else + while IFS= read -r t; do tests+=("$t"); done \ + < <(git ls-files "test/*$(basename "${f%.dart}")_test.dart") + fi + ;; + esac +done + +if [[ $full -eq 0 && ${#CHANGED[@]} -eq 0 ]]; then echo "no changes vs HEAD: nothing to test"; exit 0; fi +if [[ $full -eq 0 && ${#tests[@]} -gt 0 ]]; then flutter test --reporter=compact "${tests[@]}"; exit $?; fi +if [[ $full -eq 0 ]] && ! printf '%s\n' "${CHANGED[@]}" | grep -q '\.dart$'; then + echo "no dart changes: nothing to test"; exit 0 +fi +echo "no mapped tests: running full suite" +flutter test --reporter=compact diff --git a/repo_contract/templates/go.sh b/repo_contract/templates/go.sh new file mode 100755 index 0000000..6c666db --- /dev/null +++ b/repo_contract/templates/go.sh @@ -0,0 +1,29 @@ +#!/bin/bash + +# ============================================================================ +# Run only the tests related to files changed vs HEAD (staged, unstaged and +# untracked). Quiet: failures plus a one-line summary. Falls back to the full +# suite when a change cannot be mapped to tests. Generated by repo_contract. +# ============================================================================ + +set -euo pipefail + +cd "$(git rev-parse --show-toplevel)" +CHANGED=() +while IFS= read -r f; do + [[ -n "$f" && -e "$f" ]] && CHANGED+=("$f") +done < <({ git diff --name-only HEAD 2>/dev/null || true; git ls-files --others --exclude-standard; } | sort -u) + +pkgs=() +full=0 +for f in "${CHANGED[@]}"; do + case "$f" in + go.mod | go.sum) full=1 ;; + *.go) pkgs+=("./$(dirname "$f")") ;; + esac +done + +if [[ $full -eq 0 && ${#pkgs[@]} -eq 0 ]]; then echo "no go changes: nothing to test"; exit 0; fi +if [[ $full -eq 1 ]]; then echo "module changed: running full suite"; go test ./...; exit $?; fi +mapfile -t uniq < <(printf '%s\n' "${pkgs[@]}" | sort -u) +go test "${uniq[@]}" diff --git a/repo_contract/templates/godot.sh b/repo_contract/templates/godot.sh new file mode 100755 index 0000000..e33375e --- /dev/null +++ b/repo_contract/templates/godot.sh @@ -0,0 +1,25 @@ +#!/bin/bash + +# ============================================================================ +# Run only the tests related to files changed vs HEAD (staged, unstaged and +# untracked). Quiet: failures plus a one-line summary. Falls back to the full +# suite when a change cannot be mapped to tests. Generated by repo_contract. +# ============================================================================ + +set -euo pipefail + +cd "$(git rev-parse --show-toplevel)" +CHANGED=() +while IFS= read -r f; do + [[ -n "$f" && -e "$f" ]] && CHANGED+=("$f") +done < <({ git diff --name-only HEAD 2>/dev/null || true; git ls-files --others --exclude-standard; } | sort -u) + +# Godot test runners cannot select tests by changed file: any change runs the suite headless. +if [[ ${#CHANGED[@]} -eq 0 ]]; then echo "no changes vs HEAD: nothing to test"; exit 0; fi +if [[ -f addons/gut/gut_cmdln.gd ]]; then + godot --headless -s addons/gut/gut_cmdln.gd -gexit -glog=0 +elif [[ -f addons/gdUnit4/bin/GdUnitCmdTool.gd ]]; then + godot --headless -s -d addons/gdUnit4/bin/GdUnitCmdTool.gd -a res://test --ignoreHeadlessMode +else + echo "no GUT or gdUnit4 runner found under addons/" >&2; exit 1 +fi diff --git a/repo_contract/templates/gradle.sh b/repo_contract/templates/gradle.sh new file mode 100755 index 0000000..01ff366 --- /dev/null +++ b/repo_contract/templates/gradle.sh @@ -0,0 +1,21 @@ +#!/bin/bash + +# ============================================================================ +# Run only the tests related to files changed vs HEAD (staged, unstaged and +# untracked). Quiet: failures plus a one-line summary. Falls back to the full +# suite when a change cannot be mapped to tests. Generated by repo_contract. +# ============================================================================ + +set -euo pipefail + +cd "$(git rev-parse --show-toplevel)" +CHANGED=() +while IFS= read -r f; do + [[ -n "$f" && -e "$f" ]] && CHANGED+=("$f") +done < <({ git diff --name-only HEAD 2>/dev/null || true; git ls-files --others --exclude-standard; } | sort -u) + +# Gradle test selection by class needs per-project knowledge: any source change runs the suite. +if ! printf '%s\n' "${CHANGED[@]:-}" | grep -qE '\.(kt|java|kts|gradle|xml)$'; then + echo "no jvm source changes: nothing to test"; exit 0 +fi +./gradlew test --quiet diff --git a/repo_contract/templates/node.sh b/repo_contract/templates/node.sh new file mode 100755 index 0000000..9dad307 --- /dev/null +++ b/repo_contract/templates/node.sh @@ -0,0 +1,38 @@ +#!/bin/bash + +# ============================================================================ +# Run only the tests related to files changed vs HEAD (staged, unstaged and +# untracked). Quiet: failures plus a one-line summary. Falls back to the full +# suite when a change cannot be mapped to tests. Generated by repo_contract. +# ============================================================================ + +set -euo pipefail + +cd "$(git rev-parse --show-toplevel)" +CHANGED=() +while IFS= read -r f; do + [[ -n "$f" && -e "$f" ]] && CHANGED+=("$f") +done < <({ git diff --name-only HEAD 2>/dev/null || true; git ls-files --others --exclude-standard; } | sort -u) + +if [[ -f pnpm-lock.yaml ]]; then PM=pnpm; elif [[ -f yarn.lock ]]; then PM=yarn; else PM=npm; fi + +full=0 +src=() +for f in "${CHANGED[@]}"; do + case "$f" in + package.json | pnpm-lock.yaml | yarn.lock | package-lock.json) full=1 ;; + *.ts | *.tsx | *.js | *.jsx | *.mjs | *.cjs) src+=("$f") ;; + esac +done + +full_suite() { "$PM" test --silent; } +if [[ $full -eq 0 && ${#CHANGED[@]} -eq 0 ]]; then echo "no changes vs HEAD: nothing to test"; exit 0; fi +if [[ $full -eq 0 && ${#src[@]} -eq 0 ]]; then echo "no js/ts changes: nothing to test"; exit 0; fi +if [[ $full -eq 0 ]] && grep -qs '"vitest"' package.json; then + "$PM" exec vitest related --run --reporter=dot "${src[@]}"; exit $? +fi +if [[ $full -eq 0 ]] && grep -qs '"jest"' package.json; then + "$PM" exec jest --findRelatedTests --silent "${src[@]}"; exit $? +fi +echo "cannot map changes to tests: running full suite" +full_suite diff --git a/repo_contract/templates/pre-commit-snippet.yaml b/repo_contract/templates/pre-commit-snippet.yaml new file mode 100644 index 0000000..d788d8e --- /dev/null +++ b/repo_contract/templates/pre-commit-snippet.yaml @@ -0,0 +1,9 @@ +# Add under `repos: - repo: local: hooks:` in a repo's .pre-commit-config.yaml, +# after copying check_repo_contract.shim.sh to scripts/check_repo_contract.sh. +# pass_filenames is false: the contract is about the repo, not the staged files. + - id: repo-contract + name: repo contract (CLAUDE.md Commands + test_changed) + entry: scripts/check_repo_contract.sh + language: system + pass_filenames: false + files: ^(CLAUDE\.md|AGENTS\.md|scripts/test_changed\.sh)$ diff --git a/repo_contract/templates/python.sh b/repo_contract/templates/python.sh new file mode 100755 index 0000000..65efb87 --- /dev/null +++ b/repo_contract/templates/python.sh @@ -0,0 +1,50 @@ +#!/bin/bash + +# ============================================================================ +# Run only the tests related to files changed vs HEAD (staged, unstaged and +# untracked). Quiet: failures plus a one-line summary. Falls back to the full +# suite when a change cannot be mapped to tests. Generated by repo_contract. +# ============================================================================ + +set -euo pipefail + +cd "$(git rev-parse --show-toplevel)" +CHANGED=() +while IFS= read -r f; do + [[ -n "$f" && -e "$f" ]] && CHANGED+=("$f") +done < <({ git diff --name-only HEAD 2>/dev/null || true; git ls-files --others --exclude-standard; } | sort -u) + +pytest_run() { + if [[ -f uv.lock ]]; then uv run pytest "$@" + elif [[ -f poetry.lock ]]; then poetry run pytest "$@" + else python3 -m pytest "$@"; fi +} + +if grep -qs testmon pyproject.toml requirements*.txt; then + pytest_run --testmon -q --tb=short; exit $? +fi + +full=0 +tests=() +for f in "${CHANGED[@]}"; do + case "$f" in + */conftest.py | conftest.py | pyproject.toml | requirements*.txt | pytest.ini | setup.cfg) full=1 ;; + *.py) + base="$(basename "$f" .py)" + if [[ "$base" == test_* || "$base" == *_test ]]; then + tests+=("$f") + else + while IFS= read -r t; do tests+=("$t"); done \ + < <(git ls-files "*test_${base}.py" "*${base}_test.py") + fi + ;; + esac +done + +if [[ $full -eq 0 && ${#CHANGED[@]} -eq 0 ]]; then echo "no changes vs HEAD: nothing to test"; exit 0; fi +if [[ $full -eq 0 && ${#tests[@]} -gt 0 ]]; then pytest_run -q --tb=short "${tests[@]}"; exit $?; fi +if [[ $full -eq 0 ]] && ! printf '%s\n' "${CHANGED[@]}" | grep -q '\.py$'; then + echo "no python changes: nothing to test"; exit 0 +fi +echo "no mapped tests: running full suite" +pytest_run -q --tb=short diff --git a/repo_contract/templates/repo-contract.yml b/repo_contract/templates/repo-contract.yml new file mode 100644 index 0000000..89d5597 --- /dev/null +++ b/repo_contract/templates/repo-contract.yml @@ -0,0 +1,25 @@ +name: repo-contract + +# The local pre-commit hook can be skipped (a fresh clone has no hooks), so the +# contract is enforced on push too. Mirrors md-naming.yml. +on: + push: + branches: [main] + pull_request: + workflow_dispatch: + +jobs: + repo-contract: + name: repo contract + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Check out the shared gate + uses: actions/checkout@v4 + with: + repository: kuhyx/utils + path: .utils + + - name: Enforce the repo contract + run: bash .utils/scripts/check_repo_contract.sh diff --git a/repo_contract/templates/rust.sh b/repo_contract/templates/rust.sh new file mode 100755 index 0000000..86bddb2 --- /dev/null +++ b/repo_contract/templates/rust.sh @@ -0,0 +1,21 @@ +#!/bin/bash + +# ============================================================================ +# Run only the tests related to files changed vs HEAD (staged, unstaged and +# untracked). Quiet: failures plus a one-line summary. Falls back to the full +# suite when a change cannot be mapped to tests. Generated by repo_contract. +# ============================================================================ + +set -euo pipefail + +cd "$(git rev-parse --show-toplevel)" +CHANGED=() +while IFS= read -r f; do + [[ -n "$f" && -e "$f" ]] && CHANGED+=("$f") +done < <({ git diff --name-only HEAD 2>/dev/null || true; git ls-files --others --exclude-standard; } | sort -u) + +# cargo cannot map files to tests without extra tooling: any rust change runs the suite. +if ! printf '%s\n' "${CHANGED[@]:-}" | grep -qE '\.rs$|Cargo\.(toml|lock)$'; then + echo "no rust changes: nothing to test"; exit 0 +fi +cargo test --quiet diff --git a/repo_contract/templates/shell.sh b/repo_contract/templates/shell.sh new file mode 100755 index 0000000..54dc40f --- /dev/null +++ b/repo_contract/templates/shell.sh @@ -0,0 +1,47 @@ +#!/bin/bash + +# ============================================================================ +# Run only the tests related to files changed vs HEAD (staged, unstaged and +# untracked). Quiet: failures plus a one-line summary. Falls back to the full +# suite when a change cannot be mapped to tests. Generated by repo_contract. +# ============================================================================ + +set -euo pipefail + +cd "$(git rev-parse --show-toplevel)" +CHANGED=() +while IFS= read -r f; do + [[ -n "$f" && -e "$f" ]] && CHANGED+=("$f") +done < <({ git diff --name-only HEAD 2>/dev/null || true; git ls-files --others --exclude-standard; } | sort -u) + +tests=() +for f in "${CHANGED[@]}"; do + case "$f" in + tests/*.bats | test/*.bats) tests+=("$f") ;; + tests/test_*.sh | test/test_*.sh) tests+=("$f") ;; + *.sh) + base="$(basename "$f" .sh)" + for cand in "tests/${base}.bats" "test/${base}.bats" "tests/test_${base}.sh"; do + [[ -f "$cand" ]] && tests+=("$cand") + done + ;; + esac +done + +run_tests() { + local rc=0 out + for t in "$@"; do + if [[ "$t" == *.bats ]]; then out="$(bats -F tap "$t" 2>&1)" || rc=1 + else out="$(bash "$t" 2>&1)" || rc=1; fi + grep -v '^ok ' <<<"$out" | grep -v '^1\.\.' || true + done + echo "ran ${#} test file(s): $([[ $rc -eq 0 ]] && echo pass || echo FAIL)" + return "$rc" +} + +if [[ ${#CHANGED[@]} -eq 0 ]]; then echo "no changes vs HEAD: nothing to test"; exit 0; fi +if [[ ${#tests[@]} -gt 0 ]]; then run_tests "${tests[@]}"; exit $?; fi +mapfile -t all < <(git ls-files 'tests/*.bats' 'test/*.bats' 'tests/test_*.sh' 'test/test_*.sh') +if [[ ${#all[@]} -eq 0 ]]; then echo "no shell tests found"; exit 1; fi +echo "no mapped tests: running full suite" +run_tests "${all[@]}" diff --git a/repo_contract/tests/__init__.py b/repo_contract/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/repo_contract/tests/conftest.py b/repo_contract/tests/conftest.py new file mode 100644 index 0000000..1b23f15 --- /dev/null +++ b/repo_contract/tests/conftest.py @@ -0,0 +1,49 @@ +"""Shared fixtures for the repo_contract tests.""" + +from __future__ import annotations + +import subprocess +import sys +from pathlib import Path + +import pytest + +sys.path.insert(0, str(Path(__file__).resolve().parents[2])) + +GOOD = """# x + +## Commands + +- run: n/a: library +- test: `python3 -m pytest -q` +- test-changed: `scripts/test_changed.sh` +- lint: `ruff check .` +- coverage: `python3 -m pytest -q --cov --cov-report=xml` +- coverage-gaps: `coverage-gaps coverage.xml` +""" + + +@pytest.fixture +def repo(tmp_path: Path) -> Path: + """A throwaway git repo.""" + subprocess.run(["git", "init", "-q", str(tmp_path)], check=True) + return tmp_path + + +def write(root: Path, rel: str, body: str = "x\n", mode: int | None = None) -> Path: + """Create `rel` under `root` (parents included).""" + path = root / rel + path.parent.mkdir(parents=True, exist_ok=True) + path.write_text(body, encoding="utf-8") + if mode is not None: + path.chmod(mode) + return path + + +def good_repo(root: Path) -> Path: + """Make `root` satisfy the contract (python stack with tests).""" + write(root, "CLAUDE.md", GOOD) + write(root, "scripts/test_changed.sh", "#!/bin/bash\npython3 -m pytest -q\n", 0o755) + write(root, "pyproject.toml", "[project]\nname='x'\n") + write(root, "tests/test_a.py", "def test_a():\n pass\n") + return root diff --git a/repo_contract/tests/test_bootstrap.py b/repo_contract/tests/test_bootstrap.py new file mode 100644 index 0000000..d9f1fa0 --- /dev/null +++ b/repo_contract/tests/test_bootstrap.py @@ -0,0 +1,108 @@ +"""Bootstrap: proposals, appends, never overwriting.""" + +from __future__ import annotations + +from pathlib import Path + +import pytest + +from repo_contract.bootstrap import ( + doc_path, + insert_lines, + main, + missing_lines, + script_plan, +) +from repo_contract.check import violations +from repo_contract.infer import propose + +from .conftest import write + + +def test_insert_lines_variants() -> None: + assert insert_lines("# t\n", []) == "# t\n" + assert insert_lines("", ["- a: b"]) == "## Commands\n\n- a: b\n" + assert insert_lines("# t", ["- a: b"]) == "# t\n\n## Commands\n\n- a: b\n" + assert insert_lines("# t\n", ["- a: b"]) == "# t\n\n## Commands\n\n- a: b\n" + assert insert_lines("# t\n\n", ["- a: b"]) == "# t\n\n## Commands\n\n- a: b\n" + got = insert_lines("## Commands\n- run: `x`\n\n## After\ntext\n", ["- test: `y`"]) + assert got == "## Commands\n- run: `x`\n- test: `y`\n\n## After\ntext\n" + assert insert_lines("## Commands\n- run: `x`\n", ["- t: `y`"]).endswith( + "- t: `y`\n" + ) + + +def test_doc_path(repo: Path) -> None: + assert doc_path(repo) == repo / "CLAUDE.md" + write(repo, "AGENTS.md") + assert doc_path(repo) == repo / "AGENTS.md" + (repo / "CLAUDE.md").symlink_to("AGENTS.md") + assert doc_path(repo) == (repo / "AGENTS.md").resolve() + + +def test_missing_lines_skips_existing(repo: Path) -> None: + write(repo, "tests/test_a.py") + prop = propose(repo, ["python"]) + got = missing_lines(prop, "## Commands\n- test: `mine`\n") + assert all(not ln.startswith("- test:") for ln in got) + assert any(ln.startswith("- lint:") for ln in got) + + +def test_script_plan(repo: Path) -> None: + write(repo, "tests/test_a.py") + prop = propose(repo, ["python"]) + assert script_plan(repo, prop) is not None + write(repo, "scripts/test_changed.sh") + assert script_plan(repo, prop) is None + prop.stack = "unknown" + (repo / "scripts/test_changed.sh").unlink() + assert script_plan(repo, prop) is None + + +def test_not_a_repo(tmp_path: Path) -> None: + assert main(["--repo", str(tmp_path)]) == 2 + + +def test_dry_run_changes_nothing( + repo: Path, capsys: pytest.CaptureFixture[str] +) -> None: + write(repo, "pyproject.toml") + write(repo, "tests/test_a.py") + assert main(["--repo", str(repo)]) == 0 + out = capsys.readouterr().out + assert "- test: `python3 -m pytest -q`" in out + assert not (repo / "CLAUDE.md").exists() + assert not (repo / "scripts").exists() + + +def test_write_makes_repo_pass_and_is_idempotent( + repo: Path, capsys: pytest.CaptureFixture[str] +) -> None: + write(repo, "pyproject.toml", "[tool.ruff]\n") + write(repo, "tests/test_a.py") + write(repo, "CLAUDE.md", "# Mine\n\nKeep this.\n") + assert main(["--repo", str(repo), "--write"]) == 0 + text = (repo / "CLAUDE.md").read_text(encoding="utf-8") + assert text.startswith("# Mine\n\nKeep this.\n") and "## Commands" in text + assert (repo / "scripts/test_changed.sh").stat().st_mode & 0o111 + assert violations(repo)[0] == [] + capsys.readouterr() + assert main(["--repo", str(repo), "--write"]) == 0 + out = capsys.readouterr().out + assert "already complete" in out and "none to create" in out + assert (repo / "CLAUDE.md").read_text(encoding="utf-8") == text + + +def test_write_symlinks_claude_to_agents(repo: Path) -> None: + write(repo, "AGENTS.md", "# a\n") + write(repo, "pyproject.toml") + assert main(["--repo", str(repo), "--write"]) == 0 + assert (repo / "CLAUDE.md").is_symlink() + assert "## Commands" in (repo / "AGENTS.md").read_text(encoding="utf-8") + + +def test_write_new_claude_md(repo: Path) -> None: + write(repo, "project.godot") + assert main(["--repo", str(repo), "--write"]) == 0 + assert (repo / "CLAUDE.md").is_file() + assert violations(repo)[0] == ["'- lint:' still holds a placeholder: "] diff --git a/repo_contract/tests/test_check.py b/repo_contract/tests/test_check.py new file mode 100644 index 0000000..f13ae73 --- /dev/null +++ b/repo_contract/tests/test_check.py @@ -0,0 +1,134 @@ +"""The gate end to end.""" + +from __future__ import annotations + +from pathlib import Path + +import pytest + +from repo_contract.check import Unreadable, main, read_doc, violations + +from .conftest import GOOD, good_repo, write + + +def problems(repo: Path) -> list[str]: + return violations(repo)[0] + + +def test_good_repo_passes(repo: Path, capsys: pytest.CaptureFixture[str]) -> None: + good_repo(repo) + assert main(["--repo", str(repo)]) == 0 + assert capsys.readouterr().err == "" + + +def test_exit_2_when_not_repo(tmp_path: Path) -> None: + assert main(["--repo", str(tmp_path)]) == 2 + assert main(["--repo", str(tmp_path / "nope")]) == 2 + + +def test_exit_2_when_doc_unreadable(repo: Path) -> None: + write(repo, "CLAUDE.md").write_bytes(b"\xff\xfe\x00bad") + assert main(["--repo", str(repo)]) == 2 + with pytest.raises(Unreadable): + read_doc(repo) + + +def test_exit_1_prints_each(repo: Path, capsys: pytest.CaptureFixture[str]) -> None: + assert main(["--repo", str(repo)]) == 1 + assert "CLAUDE.md missing" in capsys.readouterr().err + + +def test_missing_doc_variants(repo: Path) -> None: + assert "or AGENTS.md" in problems(repo)[0] + write(repo, "AGENTS.md", GOOD) + assert "symlink it to AGENTS.md" in problems(repo)[0] + (repo / "CLAUDE.md").symlink_to("nowhere.md") + assert "dangling" in problems(repo)[0] + + +def test_symlinked_agents_passes(repo: Path) -> None: + good_repo(repo) + (repo / "AGENTS.md").write_text(GOOD, encoding="utf-8") + (repo / "CLAUDE.md").unlink() + (repo / "CLAUDE.md").symlink_to("AGENTS.md") + assert problems(repo) == [] + + +def test_no_section_and_missing_keys(repo: Path) -> None: + write(repo, "CLAUDE.md", "# x\n") + assert "no '## Commands'" in problems(repo)[0] + write(repo, "CLAUDE.md", "## Commands\n- test: `pytest -q`\n") + assert "missing: - run:, - test-changed:" in problems(repo)[0] + + +def test_script_rules(repo: Path) -> None: + good_repo(repo) + (repo / "scripts/test_changed.sh").chmod(0o644) + assert "not executable" in problems(repo)[0] + write(repo, "scripts/test_changed.sh", "pytest -v\n", 0o755) + assert "verbosely" in problems(repo)[0] + (repo / "scripts/test_changed.sh").unlink() + assert "existing changed-files script" in problems(repo)[0] + write(repo, "scripts/test_changed.sh").write_bytes(b"\xff\xfe") + (repo / "scripts/test_changed.sh").chmod(0o755) + with pytest.raises(Unreadable): + violations(repo) + + +def test_na_test_with_real_tests_fails(repo: Path) -> None: + good_repo(repo) + body = GOOD.replace("`python3 -m pytest -q`", "n/a: none", 1) + write(repo, "CLAUDE.md", body) + out = problems(repo) + assert any("n/a but the repo contains tests" in p for p in out) + assert any("must be n/a when test is n/a" in p for p in out) + + +def test_godot_without_tests_may_be_na(repo: Path) -> None: + na = "n/a: no tests" + body = "## Commands\n- run: `godot --path .`\n" + "".join( + f"- {k}: {na}\n" + for k in ("test", "test-changed", "lint", "coverage", "coverage-gaps") + ) + write(repo, "CLAUDE.md", body) + write(repo, "project.godot") + assert problems(repo) == [] + + +def test_real_test_cannot_have_na_chain(repo: Path) -> None: + good_repo(repo) + body = GOOD.replace("`scripts/test_changed.sh`", "n/a: why") + body = body.replace("`coverage-gaps coverage.xml`", "n/a: why") + write(repo, "CLAUDE.md", body) + out = problems(repo) + assert any("test-changed:' cannot be n/a" in p for p in out) + assert any("must run the shared lister" in p for p in out) + + +def test_wrong_lister_and_coverage_na_ok(repo: Path) -> None: + good_repo(repo) + write( + repo, + "CLAUDE.md", + GOOD.replace("`coverage-gaps coverage.xml`", "`cat coverage.xml`"), + ) + assert any("must start with `coverage-gaps" in p for p in problems(repo)) + body = GOOD.replace("`python3 -m pytest -q --cov --cov-report=xml`", "n/a: no cov") + write( + repo, "CLAUDE.md", body.replace("`coverage-gaps coverage.xml`", "n/a: no cov") + ) + assert problems(repo) == [] + + +def test_verbose_and_placeholder_reported(repo: Path) -> None: + good_repo(repo) + write( + repo, + "CLAUDE.md", + GOOD.replace("pytest -q`", "pytest -v`", 1).replace( + "ruff check .", "" + ), + ) + out = problems(repo) + assert any("quiet by default" in p for p in out) + assert any("placeholder" in p for p in out) diff --git a/repo_contract/tests/test_contract.py b/repo_contract/tests/test_contract.py new file mode 100644 index 0000000..7b2fd73 --- /dev/null +++ b/repo_contract/tests/test_contract.py @@ -0,0 +1,73 @@ +"""Parser and entry validation.""" + +from __future__ import annotations + +from pathlib import Path + +from repo_contract.contract import ( + Entry, + commands_section, + entry_problems, + has_section, + is_verbose, + parse_entries, + script_token, + verbose_in_script, +) + +from .conftest import GOOD, write + + +def test_section_extraction_respects_fences_and_next_heading() -> None: + text = "# t\n## Commands\n- a: b\n```\n## Other\n```\n## Next\n- c: d\n" + assert commands_section(text) == ["- a: b", "```", "## Other", "```"] + assert has_section(text) + assert not has_section("```\n## Commands\n```\n") + assert not has_section("## Commandsx\n") + + +def test_parse_good() -> None: + entries, problems = parse_entries(GOOD) + assert not problems + assert entries["run"] == Entry("run", None, "library") + assert entries["test"].command == "python3 -m pytest -q" + + +def test_parse_na_forms_and_errors() -> None: + text = ( + "## Commands\n- run: n/a (a game)\n- test: n/a\n- lint: n/a: \n" + "- coverage: plain words\n- test: `x`\n- lint: `a`\n- lint: `b`\n- other: `z`\nprose\n" + ) + entries, problems = parse_entries(text) + assert entries["run"].na_reason == "a game" + assert any("must carry a reason" in p for p in problems) + assert any("needs a backticked command" in p for p in problems) + assert any("duplicate '- lint:'" in p for p in problems) + assert "other" not in entries + + +def test_verbosity() -> None: + assert is_verbose("pytest -v") + assert is_verbose("pytest -vv x") + assert is_verbose("tool --verbose") + assert is_verbose("flutter test --reporter expanded") + assert not is_verbose("pytest -q") + assert is_verbose("echo 'unterminated -v") + assert not verbose_in_script("# pytest -v\ngrep -v x\npytest -q\n") + assert verbose_in_script("python3 -m pytest -v\n") + + +def test_entry_problems() -> None: + assert entry_problems(Entry("run", None, "lib")) == [] + assert entry_problems(Entry("test", "pytest -q", None)) == [] + assert entry_problems(Entry("run", "go run -v .", None)) == [] + assert len(entry_problems(Entry("test", "pytest -v", None))) == 1 + assert "placeholder" in entry_problems(Entry("lint", "", None))[0] + + +def test_script_token(tmp_path: Path) -> None: + write(tmp_path, "scripts/t.sh") + assert script_token("bash scripts/t.sh", tmp_path) == tmp_path / "scripts/t.sh" + assert script_token("scripts/missing.sh", tmp_path) is None + assert script_token("pytest -q", tmp_path) is None + assert script_token("echo 'bad scripts/t.sh", tmp_path) == tmp_path / "scripts/t.sh" diff --git a/repo_contract/tests/test_infer.py b/repo_contract/tests/test_infer.py new file mode 100644 index 0000000..d391e2d --- /dev/null +++ b/repo_contract/tests/test_infer.py @@ -0,0 +1,121 @@ +"""Command inference.""" + +from __future__ import annotations + +from pathlib import Path + +from repo_contract.infer import propose, quiet, stack_defaults + +from .conftest import write + + +def test_quiet() -> None: + assert quiet("pytest -v tests") == "pytest -q tests" + assert quiet("pytest -q") == "pytest -q" + assert quiet("flutter test") == "flutter test --reporter=compact" + assert quiet("flutter test -r expanded") == "flutter test -r expanded" + assert quiet("x --reporter=expanded") == "x " + assert quiet("go test ./...") == "go test ./..." + + +def test_python_defaults_and_launchers(tmp_path: Path) -> None: + write(tmp_path, "pyproject.toml", "[tool.ruff]\n") + assert stack_defaults(tmp_path, "python")["lint"] == "ruff check ." + write(tmp_path, "uv.lock") + assert stack_defaults(tmp_path, "python")["test"] == "uv run pytest -q" + assert stack_defaults(tmp_path, "python")["lint"] == "uv run ruff check ." + (tmp_path / "uv.lock").unlink() + write(tmp_path, "poetry.lock") + assert stack_defaults(tmp_path, "python")["test"].startswith("poetry run") + + +def test_python_no_ruff_and_run_default(tmp_path: Path) -> None: + write(tmp_path, "pyproject.toml") + write(tmp_path, "tests/test_a.py") + write(tmp_path, "main.py") + prop = propose(tmp_path, ["python"]) + assert prop.values["lint"] == "`pre-commit run --all-files`" + assert prop.values["run"] == "`python3 main.py`" + assert prop.values["coverage-gaps"] == "`coverage-gaps coverage.xml`" + assert prop.values["test-changed"] == "`scripts/test_changed.sh`" + assert prop.lines()[0].startswith("- run:") + + +def test_run_defaults_with_entry_points(tmp_path: Path) -> None: + write(tmp_path, "tests/a.txt") + for stack, marker, expected in ( + ("go", "main.go", "go run ."), + ("rust", "src/main.rs", "cargo run"), + ("gradle", "app/x", "./gradlew installDebug"), + ("shell", "x.sh", "n/a: collection of scripts"), + ): + write(tmp_path, marker) + assert propose(tmp_path, [stack]).values["run"].strip("`").startswith(expected) + + +def test_library_run_is_na(tmp_path: Path) -> None: + write(tmp_path, "tests/a.txt") + for stack in ("python", "go", "rust", "gradle", "node"): + assert propose(tmp_path, [stack]).values["run"].startswith("n/a: library") + + +def test_stack_defaults_each(tmp_path: Path) -> None: + write(tmp_path, "tests/a.txt") + write(tmp_path, "pnpm-lock.yaml") + for stack in ("flutter", "node", "go", "rust", "gradle"): + prop = propose(tmp_path, [stack]) + assert prop.values["test"].startswith("`") + assert "coverage-gaps " in prop.values["coverage-gaps"] + write(tmp_path, "yarn.lock") + (tmp_path / "pnpm-lock.yaml").unlink() + assert propose(tmp_path, ["node"]).values["test"].startswith("`yarn") + (tmp_path / "yarn.lock").unlink() + assert propose(tmp_path, ["node"]).values["test"].startswith("`npm") + + +def test_priority_doc_make_helper_npm_ci(tmp_path: Path) -> None: + write(tmp_path, "tests/a.txt") + write(tmp_path, "package.json", '{"scripts": {"test": "x", "lint": "y"}}') + assert propose(tmp_path, ["node"]).values["lint"] == "`npm run lint`" + write(tmp_path, "scripts/lint.sh") + assert propose(tmp_path, ["node"]).values["lint"] == "`scripts/lint.sh`" + write(tmp_path, "Makefile", "lint:\n\tx\n") + assert propose(tmp_path, ["node"]).values["lint"] == "`make lint`" + write(tmp_path, "CLAUDE.md", "Lint with `eslint . -v`.\n") + assert propose(tmp_path, ["node"]).values["lint"] == "`eslint .`" + write(tmp_path, ".github/workflows/a.yml", "- run: go test -v ./...\n") + assert propose(tmp_path, ["go"]).values["test"] == "`go test ./...`" + + +def test_untested_godot_and_shell_are_na(tmp_path: Path) -> None: + write(tmp_path, "project.godot") + prop = propose(tmp_path, ["godot"]) + assert prop.values["test"].startswith("n/a") + assert prop.values["test-changed"].startswith("n/a") + assert any("no tests detected" in n for n in prop.notes) + assert propose(tmp_path, []).stack == "unknown" + + +def test_godot_with_runner(tmp_path: Path) -> None: + write(tmp_path, "tests/test_a.gd") + write(tmp_path, "addons/gut/gut_cmdln.gd") + prop = propose(tmp_path, ["godot"]) + assert "gut_cmdln" in prop.values["test"] + assert prop.values["coverage-gaps"].startswith("n/a") + (tmp_path / "addons/gut/gut_cmdln.gd").unlink() + (tmp_path / "addons/gdUnit4").mkdir() + assert "GdUnitCmdTool" in propose(tmp_path, ["godot"]).values["test"] + (tmp_path / "addons/gdUnit4").rmdir() + assert propose(tmp_path, ["godot"]).values["test"] == "``" + + +def test_unknown_report_and_untested_note(tmp_path: Path) -> None: + write(tmp_path, "scripts/a.sh") + prop = propose(tmp_path, ["python"]) + assert any("no tests detected, but" in n for n in prop.notes) + write(tmp_path, "tests/a.txt") + write(tmp_path, "Makefile", "coverage:\n\tx\n") + shell = propose(tmp_path, ["shell"]) + assert shell.values["coverage"].startswith("`make coverage") + assert shell.values["coverage-gaps"] == "``" + assert any("fill in" in n for n in shell.notes) diff --git a/repo_contract/tests/test_sources.py b/repo_contract/tests/test_sources.py new file mode 100644 index 0000000..fa814ce --- /dev/null +++ b/repo_contract/tests/test_sources.py @@ -0,0 +1,63 @@ +"""Command sources: doc, Makefile, package.json, CI.""" + +from __future__ import annotations + +from pathlib import Path + +from repo_contract.sources import ( + ci_commands, + doc_commands, + first_match, + make_targets, + package_scripts, + read_text, +) + +from .conftest import write + + +def test_read_text_missing(tmp_path: Path) -> None: + assert read_text(tmp_path / "x") == "" + (tmp_path / "d").mkdir() + assert read_text(tmp_path / "d") == "" + + +def test_doc_commands() -> None: + assert doc_commands("use `pytest -q` and `scripts/x.sh` and `~/a b` and `one`") == [ + "pytest -q" + ] + + +def test_make_targets(tmp_path: Path) -> None: + write(tmp_path, "Makefile", "CC := gcc\ntest:\n\tx\nlint-all: y\n") + assert make_targets(tmp_path) == {"test", "lint-all"} + + +def test_package_scripts(tmp_path: Path) -> None: + assert package_scripts(tmp_path) == {} + write(tmp_path, "package.json", "{bad") + assert package_scripts(tmp_path) == {} + write(tmp_path, "package.json", "[1]") + assert package_scripts(tmp_path) == {} + write(tmp_path, "package.json", '{"scripts": 3}') + assert package_scripts(tmp_path) == {} + write(tmp_path, "package.json", '{"scripts": {"test": "jest"}}') + assert package_scripts(tmp_path) == {"test": "jest"} + + +def test_ci_commands(tmp_path: Path) -> None: + write( + tmp_path, + ".github/workflows/ci.yml", + "steps:\n - run: pytest -q\n - run: |\n multi\n - run: echo ${{ x }}\n run: ruff check .\n", + ) + assert ci_commands(tmp_path) == ["pytest -q", "ruff check ."] + + +def test_first_match() -> None: + cmds = ["flutter test --coverage", "flutter test", "ruff check ."] + assert first_match("test", cmds) == "flutter test" + assert first_match("coverage", cmds) == "flutter test --coverage" + assert first_match("lint", cmds) == "ruff check ." + assert first_match("run", cmds) is None + assert first_match("lint", ["x"]) is None diff --git a/repo_contract/tests/test_stacks.py b/repo_contract/tests/test_stacks.py new file mode 100644 index 0000000..75851fd --- /dev/null +++ b/repo_contract/tests/test_stacks.py @@ -0,0 +1,76 @@ +"""Stack detection and the test-presence probe.""" + +from __future__ import annotations + +from pathlib import Path + +import pytest + +from repo_contract.stacks import MAX_DEPTH, detect_stacks, has_tests + +from .conftest import write + + +def test_markers_detected(tmp_path: Path) -> None: + write(tmp_path, "pubspec.yaml") + write(tmp_path, "pyproject.toml") + assert detect_stacks(tmp_path) == ["flutter", "python"] + + +def test_each_marker(tmp_path: Path) -> None: + for name, stack in ( + ("project.godot", "godot"), + ("go.mod", "go"), + ("Cargo.toml", "rust"), + ("build.gradle.kts", "gradle"), + ("package.json", "node"), + ): + sub = tmp_path / stack + write(sub, name) + assert detect_stacks(sub) == [stack] + + +def test_shell_only_and_unknown(tmp_path: Path) -> None: + assert detect_stacks(tmp_path) == [] + write(tmp_path, "scripts/a.sh") + assert detect_stacks(tmp_path) == ["shell"] + + +def test_walk_prunes_and_bounds_depth(tmp_path: Path) -> None: + write(tmp_path, "node_modules/x/y_test.py") + deep = "/".join(["d"] * (MAX_DEPTH + 1)) + write(tmp_path, f"{deep}/test_deep.py") + assert not has_tests(tmp_path, []) + + +def test_has_tests_by_dir_and_name(tmp_path: Path) -> None: + write(tmp_path / "a", "tests/anything.txt") + assert has_tests(tmp_path / "a", []) + write(tmp_path / "b", "src/foo_test.go") + assert has_tests(tmp_path / "b", []) + write(tmp_path / "c", "test_x.py") + assert has_tests(tmp_path / "c", []) + write(tmp_path / "d", "src/a.py") + assert not has_tests(tmp_path / "d", []) + + +def test_node_test_script(tmp_path: Path) -> None: + write(tmp_path, "package.json", '{"scripts": {"test": "vitest"}}') + assert has_tests(tmp_path, ["node"]) + write(tmp_path, "package.json", '{"scripts": {"test": "echo no test specified"}}') + assert not has_tests(tmp_path, ["node"]) + assert not has_tests(tmp_path, ["python"]) + + +def test_node_package_json_unreadable(tmp_path: Path) -> None: + (tmp_path / "package.json").mkdir() + assert not has_tests(tmp_path, ["node"]) + + +def test_file_cap(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None: + from repo_contract import stacks + + monkeypatch.setattr(stacks, "MAX_FILES", 2) + for i in range(5): + write(tmp_path, f"f{i}.txt") + assert len(stacks._walk(tmp_path)) >= 2 diff --git a/repo_contract/tests/test_sweep.py b/repo_contract/tests/test_sweep.py new file mode 100644 index 0000000..66b22fc --- /dev/null +++ b/repo_contract/tests/test_sweep.py @@ -0,0 +1,45 @@ +"""Sweep report.""" + +from __future__ import annotations + +import subprocess +from pathlib import Path + +import pytest + +from repo_contract.sweep import Result, find_repos, main, render, run_one + +from .conftest import good_repo, write + + +def make_repo(path: Path) -> Path: + path.mkdir(parents=True) + subprocess.run(["git", "init", "-q", str(path)], check=True) + return path + + +def test_find_repos_skips_dot_and_non_repos(tmp_path: Path) -> None: + make_repo(tmp_path / "a") + make_repo(tmp_path / ".hidden") + (tmp_path / "plain").mkdir() + assert find_repos([tmp_path, tmp_path / "missing"]) == [tmp_path / "a"] + + +def test_run_one_and_render(tmp_path: Path) -> None: + ok = good_repo(make_repo(tmp_path / "ok")) + bad = make_repo(tmp_path / "bad") + write(bad, "CLAUDE.md").write_bytes(b"\xff\xfe") + results = [run_one(ok), run_one(bad), Result("x", tmp_path, [], ["a|b"])] + assert [r.passed for r in results] == [True, False, False] + text = render(results) + assert "3 repos: 1 pass, 2 fail." in text + assert "| python | 1 | 0 |" in text and "| none | 2 | 2 |" in text + assert "a/b" in text and "unreadable" in text + + +def test_main_writes_report(tmp_path: Path, capsys: pytest.CaptureFixture[str]) -> None: + make_repo(tmp_path / "r") + out = tmp_path / "out" / "SWEEP.md" + assert main(roots=[tmp_path], out=out) == 0 + assert "1 repos, 0 pass, 1 fail" in capsys.readouterr().out + assert out.is_file() diff --git a/repo_contract/tests/test_templates.py b/repo_contract/tests/test_templates.py new file mode 100644 index 0000000..776e38f --- /dev/null +++ b/repo_contract/tests/test_templates.py @@ -0,0 +1,109 @@ +"""Run the generated test_changed.sh scripts against real throwaway repos.""" + +from __future__ import annotations + +import subprocess +from pathlib import Path + +from repo_contract.bootstrap import TEMPLATES + +from .conftest import write + + +def run( + repo: Path, stack: str, *, env_path: str | None = None +) -> subprocess.CompletedProcess[str]: + subprocess.run(["git", "-C", str(repo), "add", "-A"], check=True) + subprocess.run( + [ + "git", + "-C", + str(repo), + "-c", + "user.name=t", + "-c", + "user.email=t@t", + "commit", + "-qm", + "i", + ], + check=True, + ) + return subprocess.run( + ["bash", str(TEMPLATES / f"{stack}.sh")], + cwd=repo, + capture_output=True, + text=True, + check=False, + ) + + +def test_python_runs_only_mapped_tests(repo: Path) -> None: + write(repo, "pkg/a.py", "x = 1\n") + write(repo, "tests/test_a.py", "def test_a():\n assert True\n") + write(repo, "tests/test_b.py", "def test_b():\n assert False\n") + done = run(repo, "python") + assert "nothing to test" in done.stdout and done.returncode == 0 + write(repo, "pkg/a.py", "x = 2\n") + done = subprocess.run( + ["bash", str(TEMPLATES / "python.sh")], + cwd=repo, + capture_output=True, + text=True, + check=False, + ) + assert done.returncode == 0 and "1 passed" in done.stdout + subprocess.run(["git", "-C", str(repo), "add", "-A"], check=True) + subprocess.run( + [ + "git", + "-C", + str(repo), + "-c", + "user.name=t", + "-c", + "user.email=t@t", + "commit", + "-qm", + "2", + ], + check=True, + ) + write(repo, "pkg/c.py", "y = 1\n") + done = subprocess.run( + ["bash", str(TEMPLATES / "python.sh")], + cwd=repo, + capture_output=True, + text=True, + check=False, + ) + assert "full suite" in done.stdout and done.returncode != 0 + + +def test_python_ignores_non_python_changes(repo: Path) -> None: + write(repo, "README.md") + run(repo, "python") + write(repo, "README.md", "changed\n") + done = subprocess.run( + ["bash", str(TEMPLATES / "python.sh")], + cwd=repo, + capture_output=True, + text=True, + check=False, + ) + assert "no python changes" in done.stdout + + +def test_shell_template(repo: Path) -> None: + write(repo, "scripts/a.sh", "echo a\n") + write(repo, "tests/test_a.sh", "exit 0\n") + run(repo, "shell") + write(repo, "scripts/a.sh", "echo b\n") + done = subprocess.run( + ["bash", str(TEMPLATES / "shell.sh")], + cwd=repo, + capture_output=True, + text=True, + check=False, + ) + assert done.returncode == 0 and "pass" in done.stdout diff --git a/scripts/check_repo_contract.sh b/scripts/check_repo_contract.sh new file mode 100755 index 0000000..04986db --- /dev/null +++ b/scripts/check_repo_contract.sh @@ -0,0 +1,25 @@ +#!/bin/bash + +# ============================================================================ +# Fail if the current repo breaks the repo contract: CLAUDE.md `## Commands` +# with run / test / test-changed / lint / coverage / coverage-gaps entries and +# a quiet scripts/test_changed.sh. See repo_contract/README.md. +# +# This is the REAL gate. Per-repo scripts/check_repo_contract.sh files are thin +# shims that exec this one -- do not overwrite this file with a shim. +# +# Usage: scripts/check_repo_contract.sh [--repo PATH] # default: cwd +# Exit: 0 pass, 1 violations, 2 not a repo / unreadable. +# ============================================================================ + +set -euo pipefail + +UTILS_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +readonly UTILS_ROOT + +main() { + PYTHONPATH="$UTILS_ROOT${PYTHONPATH:+:$PYTHONPATH}" \ + python3 "$UTILS_ROOT/repo_contract/check.py" "$@" +} + +main "$@" diff --git a/scripts/test_changed.sh b/scripts/test_changed.sh new file mode 100755 index 0000000..1b6346f --- /dev/null +++ b/scripts/test_changed.sh @@ -0,0 +1,48 @@ +#!/bin/bash + +# ============================================================================ +# Run only the tests related to files changed vs HEAD (staged, unstaged and +# untracked). Quiet: failures plus a one-line summary. Maps a change to its +# top-level directory: `/tests` runs with PYTHONPATH=., a subproject with +# its own pyproject.toml runs via scripts/run_subproject_tests.sh. Anything +# unmappable falls back to the shared-gate suites. +# ============================================================================ + +set -euo pipefail + +cd "$(git rev-parse --show-toplevel)" +CHANGED=() +while IFS= read -r f; do + [[ -n "$f" && -e "$f" ]] && CHANGED+=("$f") +done < <({ git diff --name-only HEAD 2>/dev/null || true; git ls-files --others --exclude-standard; } | sort -u) + +readonly GATES=(coverage_gaps dep_freshness file_length home_paths md_naming repo_contract) +dirs=() +unmapped=0 +for f in "${CHANGED[@]}"; do + d="${f%%/*}" + case "$f" in + *.py | *.sh | *.yaml | *.yml | *.toml | *.json) ;; + *) continue ;; + esac + if [[ "$d" == "$f" ]]; then unmapped=1; continue; fi + if [[ -d "$d/tests" || -f "$d/pyproject.toml" ]]; then dirs+=("$d"); else unmapped=1; fi +done +mapfile -t dirs < <(printf '%s\n' "${dirs[@]}" | sort -u) + +if [[ ${#CHANGED[@]} -eq 0 ]]; then echo "no changes vs HEAD: nothing to test"; exit 0; fi +if [[ ${#dirs[@]} -eq 0 && $unmapped -eq 0 ]]; then echo "no code changes: nothing to test"; exit 0; fi + +rc=0 +if [[ ${#dirs[@]} -eq 0 ]]; then + echo "no mapped tests: running shared-gate suites" + dirs=("${GATES[@]}") +fi +for d in "${dirs[@]}"; do + if [[ -f "$d/pyproject.toml" ]]; then + scripts/run_subproject_tests.sh "$d" -q --tb=short || rc=$? + elif [[ -d "$d/tests" ]]; then + PYTHONPATH=. python3 -m pytest -q --tb=short "$d/tests" || rc=$? + fi +done +exit $rc diff --git a/ts_config/README.md b/ts_config/README.md index bcbb425..45a5798 100644 --- a/ts_config/README.md +++ b/ts_config/README.md @@ -72,14 +72,15 @@ needs them rewritten before the rule can go on. That is a script, not a session's worth of hand edits: ```sh -node ~/src/utils/ts_config/scripts/alias-imports.mjs [src-dir] +node ~/src/utils/ts_config/scripts/alias-imports.mjs [src-dir] [extra-dir...] ``` Every `import` / `export … from` / `import()` specifier under `src/` that starts with `../` and resolves inside `src/` becomes `@/`; `./sibling`, package imports and a `../` that leaves `src/` are left alone (the last one is the lint report's to surface). Extensions are carried over -untouched. Idempotent. The repo still needs the alias itself: `paths` in +untouched. Idempotent. Extra directories (`tests`, `scripts`) are walked +too and aliased against `src`. The repo still needs the alias itself: `paths` in tsconfig (see above) and `resolve.alias` in vite.config, which vitest reads. `test/alias-imports.test.js` runs it over a fixture tree. diff --git a/ts_config/eslint.config.js b/ts_config/eslint.config.js index 2a2018f..29ea5dc 100644 --- a/ts_config/eslint.config.js +++ b/ts_config/eslint.config.js @@ -6,5 +6,13 @@ import { defineConfig } from "./eslint/index.js"; export default defineConfig({ aliasOnly: false, ignores: ["test/fixture"], + overrides: [ + { + // fast.js IS a preset: its default export is the built config, so the + // call at the top level is the module's whole purpose. + files: ["eslint/fast.js"], + rules: { "unicorn/no-top-level-side-effects": "off" }, + }, + ], tsconfigRootDir: import.meta.dirname, }); diff --git a/ts_config/eslint/overrides.js b/ts_config/eslint/overrides.js index 244dafd..d909a0b 100644 --- a/ts_config/eslint/overrides.js +++ b/ts_config/eslint/overrides.js @@ -11,6 +11,13 @@ /** @type {import("eslint").Linter.Config[]} */ export const overrides = [ + { + // A tool config file exists to `export default` the result of a call + // (`defineConfig(...)`); that is its entire job, not a hidden side + // effect, so the module-purity rule has nothing to protect here. + files: ["**/*.config.{js,mjs,cjs,ts,mts,cts}"], + rules: { "unicorn/no-top-level-side-effects": "off" }, + }, { // A package's entry point IS a barrel: its whole job is to name the // public API. The rule stays on everywhere else, where a barrel is how diff --git a/ts_config/package.json b/ts_config/package.json index 91c7087..612c775 100644 --- a/ts_config/package.json +++ b/ts_config/package.json @@ -9,7 +9,7 @@ "node": ">=24", "pnpm": ">=10" }, - "packageManager": "pnpm@12.6.0+sha512.3ef68f951cb111ac204b4a5a16f0b2ddf0da56a96e0413e81d855d9f0b55ef926714709028e1cd00c405c2c5fb7b9e8ec4dc46777c805d0373c2f2ff00fd20ec", + "packageManager": "pnpm@12.9.0+sha512.8f64efa0b792986ca98ae568791bcd7722807e274c4f0c1d9853c62116495c584119db98762a2a5343b1684d361b9ae26fdd16d551666d7635346d3fae830ddd", "files": [ "eslint", "tsconfig.base.json", @@ -39,7 +39,7 @@ "coverage": "vitest run --coverage" }, "dependencies": { - "@eslint-react/eslint-plugin": "5.20.8", + "@eslint-react/eslint-plugin": "5.23.5", "@eslint/js": "10.0.1", "@vitest/eslint-plugin": "1.6.27", "eslint-import-resolver-typescript": "4.4.5", @@ -50,9 +50,9 @@ "eslint-plugin-react-hooks": "7.1.1", "eslint-plugin-react-refresh": "0.5.7", "eslint-plugin-react-you-might-not-need-an-effect": "1.0.2", - "eslint-plugin-sonarjs": "4.2.1", - "eslint-plugin-unicorn": "76.0.0", - "globals": "17.12.0" + "eslint-plugin-sonarjs": "4.2.2", + "eslint-plugin-unicorn": "77.0.0", + "globals": "17.13.0" }, "peerDependencies": { "eslint": ">=10", @@ -62,11 +62,11 @@ "devDependencies": { "@playwright/test": "1.63.0", "@types/react": "19.3.0", - "@vitest/coverage-v8": "5.0.2", - "eslint": "10.11.0", + "@vitest/coverage-v8": "5.0.3", + "eslint": "10.12.0", "react": "19.3.0", "typescript": "6.0.3", - "typescript-eslint": "8.70.0", - "vitest": "5.0.2" + "typescript-eslint": "8.71.0", + "vitest": "5.0.3" } } diff --git a/ts_config/pnpm-lock.yaml b/ts_config/pnpm-lock.yaml index c2953d1..830ef7e 100644 --- a/ts_config/pnpm-lock.yaml +++ b/ts_config/pnpm-lock.yaml @@ -7,153 +7,153 @@ importers: configDependencies: {} packageManagerDependencies: pnpm: - specifier: 12.6.0 - version: 12.6.0 + specifier: 12.9.0 + version: 12.9.0 packages: - '@pnpm/exe.android-arm64@12.6.0': - resolution: {integrity: sha512-kviIHft9h02q+7N2In7tSL9T/HRUSGwYmU74YPtsWU5ee7vgqPyC0JT6RRX6miW+NxX1CvHgQxg69qq1qTLJYQ==} + '@pnpm/exe.android-arm64@12.9.0': + resolution: {integrity: sha512-/53fHFwP1mBYQjli6h3LBZsocAMjrEY6hCDiomIwdLIFbtxHrWucPg/s8ozOG3K6Qt8yEA78v7fCcNrgZKTlEQ==} cpu: [arm64] os: [android] - '@pnpm/exe.android-x64@12.6.0': - resolution: {integrity: sha512-CT8aJKLq2mtZFE71pr4E5Z2xHL8uGnRGr+NclDqsXlVF4SVcQ2QAs14mWi0C8gjT4hbmi+Q0lJRRIkMAWfjjng==} + '@pnpm/exe.android-x64@12.9.0': + resolution: {integrity: sha512-QI+Md3q3Df0WbDGpqciRw9ge0r7CGbxSlP3HcyIf34/YdZsbmUTl416oWOe3RrkZpVVVCA20f8B9esYrITpArA==} cpu: [x64] os: [android] - '@pnpm/exe.darwin-arm64@12.6.0': - resolution: {integrity: sha512-rafpVkjzugKBMxSvGQd5wK1x5eOTkD/+lcJIFHeQhvd+BJ8kDisOOvwhQDrpGd4vd2Fx+hhW0P2Ptt638OlhFg==} + '@pnpm/exe.darwin-arm64@12.9.0': + resolution: {integrity: sha512-mWr7fGPYJ12GuvU/q0SyJfj7X/9Eqa5dA6cc+yUUAKRdYEcqW4lQcsDd1ArfxJNSDYooz9X+UhFeVxKO9e/xDA==} cpu: [arm64] os: [darwin] - '@pnpm/exe.darwin-x64@12.6.0': - resolution: {integrity: sha512-72Jpuv1m24gI8zUNcaylYpBWEhBzz1VPhnDIU2GYM2eRP+KsOqVLwV78i+JrihI39zY2NNXmaN4eAKX1inpaDQ==} + '@pnpm/exe.darwin-x64@12.9.0': + resolution: {integrity: sha512-fr4ns4FyMLptdYbWqW3yc/yvbfQP7D1RBCFUJpSPr7tOzd3JxZvl+Tkvg65kEAGU5wXI6vl3eCOofumEj/n3Gw==} cpu: [x64] os: [darwin] - '@pnpm/exe.freebsd-x64@12.6.0': - resolution: {integrity: sha512-LON4QgNy1w/XF4uySZIyC/5hEB0oGdelXsY9tSKqstER+2j2X2oHz67skD11RRd/HWFRtMr64shQZ4I9ZoZ0zg==} + '@pnpm/exe.freebsd-x64@12.9.0': + resolution: {integrity: sha512-4+hZPeJzSSw1oOrfUg+v4QylHv6qKhdgcCnKt9ooryE79p1Z9oyozVJ5em+g6ajbvv6jlH+epnht8/s9Cp5M3Q==} cpu: [x64] os: [freebsd] - '@pnpm/exe.linux-arm64-musl@12.6.0': - resolution: {integrity: sha512-BdDpX+DeaMUc5x6WNcE6FkmFy36NwrBbBvwsZU737Zt3u1fTY+iKFPchjEQNCM4rT6q6xvF2oN3ZwExGsJCkxQ==} + '@pnpm/exe.linux-arm64-musl@12.9.0': + resolution: {integrity: sha512-+mHQSbA6M6W71PyV0IxYu2T43lZA8rAhlw4Gy7EMlyiv8H2GWJAXtUXl2huw7tnsar2AhCRlPn/ZvUv+/mIBkA==} cpu: [arm64] os: [linux] libc: [musl] - '@pnpm/exe.linux-arm64@12.6.0': - resolution: {integrity: sha512-8h2sNoIhHDpHDYqZZCh9PXr6krqW10btb0GHVGuw710muDHlFAHMtUG2ogmaHge6zJG1xgY4Y7mwX5g/LWvrYg==} + '@pnpm/exe.linux-arm64@12.9.0': + resolution: {integrity: sha512-U1mBoG27+J/HuWYtVZOQrvbiKnTlDsDVzyroKQqHsJaTHTXi0H5Y+fU1GjUyMcNG5r/EXtbhp9iAwQt64uXTxQ==} cpu: [arm64] os: [linux] libc: [glibc] - '@pnpm/exe.linux-ppc64@12.6.0': - resolution: {integrity: sha512-ld6xgcEhFCFrsVsJhbmdmR714PnsEspO/Ij8w0/D7OSiz2hYGu3fZd5tKRxOI5m6H6Gl0/RRFAPncTHbsgXVjQ==} + '@pnpm/exe.linux-ppc64@12.9.0': + resolution: {integrity: sha512-P5bgei3i6wjlKq6s4PNg/0cvik354upY2wW3YGSfweYTABnAZP2zGzgMc0eO5SHTz934Cbya8Z0YFRdw6MfmCg==} cpu: [ppc64] os: [linux] libc: [glibc] - '@pnpm/exe.linux-riscv64@12.6.0': - resolution: {integrity: sha512-HIbImydoC3J8NFt+8MXfhTCf5rO6NjCMyinSAHac0VmV8uCHw+kbQkyAzxV84rzz9ZOzmBOnj0b1N461UIYGsw==} + '@pnpm/exe.linux-riscv64@12.9.0': + resolution: {integrity: sha512-oNx2uOo2gVrRckEvz7V+FKeq9HOF0rKNHcCyxRRHSXEXpmP6ZMMgJ+iU/m/zfSGp6bAcC5mvAE270FhAeIeKhA==} cpu: [riscv64] os: [linux] libc: [glibc] - '@pnpm/exe.linux-s390x@12.6.0': - resolution: {integrity: sha512-DjSqT7+BZ/lWcDHwNMuzsRVfyxUimh+D54Xw4tSWbdVVQhtWtNMUyI93LVnwCmsoEpar2gC3SFeAgywx37svKQ==} + '@pnpm/exe.linux-s390x@12.9.0': + resolution: {integrity: sha512-OChHS7hOlqdBMtZqjIB1QoZGNfzLPSsvTa2kYanf3qso5Q+KgnROQ+uV4a0wRGC6sdshSA63+Ui8MCylRy6iwA==} cpu: [s390x] os: [linux] libc: [glibc] - '@pnpm/exe.linux-x64-musl@12.6.0': - resolution: {integrity: sha512-31lKeGPmRE6xfV6I3VjaEGVFRei14pl/zgoFDK4w+UkbJqOuNl2Ht3O1GuqNIg3Gl14qTEb4kZRAYFHXifDx9A==} + '@pnpm/exe.linux-x64-musl@12.9.0': + resolution: {integrity: sha512-ubZPsDMXS+LEnPa7wZD18E+KTpOAZPt+ms/khfIROt+6Cp0A2kayQA8aIsvV7+jGZD0xoknvaGN/P3v9OisZUg==} cpu: [x64] os: [linux] libc: [musl] - '@pnpm/exe.linux-x64@12.6.0': - resolution: {integrity: sha512-qFWBneHJAJ73W4whtbaFOL1M/7DBC6ILHXuxc7ZPtEhfPuT1zeZiGrmKHoMAfJA+mcm6xhOFljqVTUS+00Jabw==} + '@pnpm/exe.linux-x64@12.9.0': + resolution: {integrity: sha512-ElANYUwQuMfWqMJR4m5L5h9zKjvGKcA8hi2put4J1DMx2smVaNGkGnxkgeIYgMjmF7UFxlZDcL4jItkScgsW4A==} cpu: [x64] os: [linux] libc: [glibc] - '@pnpm/exe.win32-arm64@12.6.0': - resolution: {integrity: sha512-OhfefXEEykZlslSUhR8PPRe6MVPV3Oink/dVfIecE5velpc7j80cZYsHlaDrtRDcm3TPwQHkaRW1SJ1efX9oFg==} + '@pnpm/exe.win32-arm64@12.9.0': + resolution: {integrity: sha512-cqnBKZg5e9fFghmYtgJLyZOAsS1EyLeDVJ0qNsjR7hcc+dq1mWRiDo0erqdj2PGoYaVogyF9WejZSJoKvlfXDw==} cpu: [arm64] os: [win32] - '@pnpm/exe.win32-x64@12.6.0': - resolution: {integrity: sha512-L2tuyrD2+Imgxs3VK/ST/2L3xD6vDP0ktJQxM0TaGw7TbAQEMq+RMc8iK6Ew7Id579uzjJe8jSfJID2ZGsOHCA==} + '@pnpm/exe.win32-x64@12.9.0': + resolution: {integrity: sha512-khqst6QGgHdKs2lEmz0N75qN/EoOcEywNQlRPfBrBrq1SytHra6ro/xViueJkVvDOz9gEO8oYcXTdZeAjErjMg==} cpu: [x64] os: [win32] - pnpm@12.6.0: - resolution: {integrity: sha512-PvaPlRyxEawgS0paFvCy3fDaVqluBBPoHYVdnwtV75JnFHCQKOHNAMQFwsX7e56OxNxGd3yAXQNzwvL/AP0g7A==} + pnpm@12.9.0: + resolution: {integrity: sha512-j2TvoLeSmGypiuVoeRvNdyKAfidMTwwdmFPGIRZJXFhBGduYdioqU0OxaE02G5rib90W1VFmbXY1NG0/roMN3Q==} engines: {node: '>=18.*'} hasBin: true snapshots: - '@pnpm/exe.android-arm64@12.6.0': + '@pnpm/exe.android-arm64@12.9.0': optional: true - '@pnpm/exe.android-x64@12.6.0': + '@pnpm/exe.android-x64@12.9.0': optional: true - '@pnpm/exe.darwin-arm64@12.6.0': + '@pnpm/exe.darwin-arm64@12.9.0': optional: true - '@pnpm/exe.darwin-x64@12.6.0': + '@pnpm/exe.darwin-x64@12.9.0': optional: true - '@pnpm/exe.freebsd-x64@12.6.0': + '@pnpm/exe.freebsd-x64@12.9.0': optional: true - '@pnpm/exe.linux-arm64-musl@12.6.0': + '@pnpm/exe.linux-arm64-musl@12.9.0': optional: true - '@pnpm/exe.linux-arm64@12.6.0': + '@pnpm/exe.linux-arm64@12.9.0': optional: true - '@pnpm/exe.linux-ppc64@12.6.0': + '@pnpm/exe.linux-ppc64@12.9.0': optional: true - '@pnpm/exe.linux-riscv64@12.6.0': + '@pnpm/exe.linux-riscv64@12.9.0': optional: true - '@pnpm/exe.linux-s390x@12.6.0': + '@pnpm/exe.linux-s390x@12.9.0': optional: true - '@pnpm/exe.linux-x64-musl@12.6.0': + '@pnpm/exe.linux-x64-musl@12.9.0': optional: true - '@pnpm/exe.linux-x64@12.6.0': + '@pnpm/exe.linux-x64@12.9.0': optional: true - '@pnpm/exe.win32-arm64@12.6.0': + '@pnpm/exe.win32-arm64@12.9.0': optional: true - '@pnpm/exe.win32-x64@12.6.0': + '@pnpm/exe.win32-x64@12.9.0': optional: true - pnpm@12.6.0: + pnpm@12.9.0: optionalDependencies: - '@pnpm/exe.android-arm64': 12.6.0 - '@pnpm/exe.android-x64': 12.6.0 - '@pnpm/exe.darwin-arm64': 12.6.0 - '@pnpm/exe.darwin-x64': 12.6.0 - '@pnpm/exe.freebsd-x64': 12.6.0 - '@pnpm/exe.linux-arm64': 12.6.0 - '@pnpm/exe.linux-arm64-musl': 12.6.0 - '@pnpm/exe.linux-ppc64': 12.6.0 - '@pnpm/exe.linux-riscv64': 12.6.0 - '@pnpm/exe.linux-s390x': 12.6.0 - '@pnpm/exe.linux-x64': 12.6.0 - '@pnpm/exe.linux-x64-musl': 12.6.0 - '@pnpm/exe.win32-arm64': 12.6.0 - '@pnpm/exe.win32-x64': 12.6.0 + '@pnpm/exe.android-arm64': 12.9.0 + '@pnpm/exe.android-x64': 12.9.0 + '@pnpm/exe.darwin-arm64': 12.9.0 + '@pnpm/exe.darwin-x64': 12.9.0 + '@pnpm/exe.freebsd-x64': 12.9.0 + '@pnpm/exe.linux-arm64': 12.9.0 + '@pnpm/exe.linux-arm64-musl': 12.9.0 + '@pnpm/exe.linux-ppc64': 12.9.0 + '@pnpm/exe.linux-riscv64': 12.9.0 + '@pnpm/exe.linux-s390x': 12.9.0 + '@pnpm/exe.linux-x64': 12.9.0 + '@pnpm/exe.linux-x64-musl': 12.9.0 + '@pnpm/exe.win32-arm64': 12.9.0 + '@pnpm/exe.win32-x64': 12.9.0 --- lockfileVersion: '9.0' @@ -167,47 +167,47 @@ importers: .: dependencies: '@eslint-react/eslint-plugin': - specifier: 5.20.8 - version: 5.20.8(eslint@10.11.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) + specifier: 5.23.5 + version: 5.23.5(eslint@10.12.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) '@eslint/js': specifier: 10.0.1 - version: 10.0.1(eslint@10.11.0(supports-color@7.2.0)) + version: 10.0.1(eslint@10.12.0(supports-color@7.2.0)) '@vitest/eslint-plugin': specifier: 1.6.27 - version: 1.6.27(@typescript-eslint/eslint-plugin@8.70.0(@typescript-eslint/parser@8.70.0(eslint@10.11.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3))(eslint@10.11.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3))(eslint@10.11.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3)(vitest@5.0.2) + version: 1.6.27(@typescript-eslint/eslint-plugin@8.71.0(@typescript-eslint/parser@8.71.0(eslint@10.12.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3))(eslint@10.12.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3))(eslint@10.12.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3)(vitest@5.0.3) eslint-import-resolver-typescript: specifier: 4.4.5 - version: 4.4.5(eslint@10.11.0(supports-color@7.2.0))(supports-color@7.2.0) + version: 4.4.5(eslint@10.12.0(supports-color@7.2.0))(supports-color@7.2.0) eslint-plugin-boundaries: specifier: 7.2.0 - version: 7.2.0(@typescript-eslint/parser@8.70.0(eslint@10.11.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3))(eslint-import-resolver-typescript@4.4.5(eslint@10.11.0(supports-color@7.2.0))(supports-color@7.2.0))(eslint@10.11.0(supports-color@7.2.0))(supports-color@7.2.0) + version: 7.2.0(@typescript-eslint/parser@8.71.0(eslint@10.12.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3))(eslint-import-resolver-typescript@4.4.5(eslint@10.12.0(supports-color@7.2.0))(supports-color@7.2.0))(eslint@10.12.0(supports-color@7.2.0))(supports-color@7.2.0) eslint-plugin-jsx-a11y: specifier: 6.10.2 - version: 6.10.2(eslint@10.11.0(supports-color@7.2.0)) + version: 6.10.2(eslint@10.12.0(supports-color@7.2.0)) eslint-plugin-perfectionist: specifier: 5.12.1 - version: 5.12.1(eslint@10.11.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) + version: 5.12.1(eslint@10.12.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) eslint-plugin-playwright: specifier: 2.12.0 - version: 2.12.0(eslint@10.11.0(supports-color@7.2.0)) + version: 2.12.0(eslint@10.12.0(supports-color@7.2.0)) eslint-plugin-react-hooks: specifier: 7.1.1 - version: 7.1.1(eslint@10.11.0(supports-color@7.2.0))(supports-color@7.2.0) + version: 7.1.1(eslint@10.12.0(supports-color@7.2.0))(supports-color@7.2.0) eslint-plugin-react-refresh: specifier: 0.5.7 - version: 0.5.7(eslint@10.11.0(supports-color@7.2.0)) + version: 0.5.7(eslint@10.12.0(supports-color@7.2.0)) eslint-plugin-react-you-might-not-need-an-effect: specifier: 1.0.2 - version: 1.0.2(eslint@10.11.0(supports-color@7.2.0)) + version: 1.0.2(eslint@10.12.0(supports-color@7.2.0)) eslint-plugin-sonarjs: - specifier: 4.2.1 - version: 4.2.1(eslint@10.11.0(supports-color@7.2.0)) + specifier: 4.2.2 + version: 4.2.2(eslint@10.12.0(supports-color@7.2.0)) eslint-plugin-unicorn: - specifier: 76.0.0 - version: 76.0.0(eslint@10.11.0(supports-color@7.2.0)) + specifier: 77.0.0 + version: 77.0.0(eslint@10.12.0(supports-color@7.2.0)) globals: - specifier: 17.12.0 - version: 17.12.0 + specifier: 17.13.0 + version: 17.13.0 devDependencies: '@playwright/test': specifier: 1.63.0 @@ -216,11 +216,11 @@ importers: specifier: 19.3.0 version: 19.3.0 '@vitest/coverage-v8': - specifier: 5.0.2 - version: 5.0.2(vitest@5.0.2) + specifier: 5.0.3 + version: 5.0.3(vitest@5.0.3) eslint: - specifier: 10.11.0 - version: 10.11.0(supports-color@7.2.0) + specifier: 10.12.0 + version: 10.12.0(supports-color@7.2.0) react: specifier: 19.3.0 version: 19.3.0 @@ -228,11 +228,11 @@ importers: specifier: 6.0.3 version: 6.0.3 typescript-eslint: - specifier: 8.70.0 - version: 8.70.0(eslint@10.11.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) + specifier: 8.71.0 + version: 8.71.0(eslint@10.12.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) vitest: - specifier: 5.0.2 - version: 5.0.2(@vitest/coverage-v8@5.0.2)(vite@8.3.0(yaml@2.9.1)) + specifier: 5.0.3 + version: 5.0.3(@vitest/coverage-v8@5.0.3)(vite@8.3.0(yaml@2.9.1)) packages: @@ -336,54 +336,75 @@ packages: resolution: {integrity: sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==} engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} - '@eslint-react/ast@5.20.8': - resolution: {integrity: sha512-p2LpsgLAbSpn6/TboNRpasnOAMLsSxtPamX9QevRrtbxs1K3aLKWyjuM+mO2D1VJhagknjeyC1x+d4v6yoWf5w==} + '@eslint-react/ast@5.23.5': + resolution: {integrity: sha512-QO8QhYOvIaO651nEC76At614y2jRyqwdF97mQansEbpBwgdnYmS9Sj/Mad0lUGWTBCX2QVUA6aoox62AgMXw0Q==} engines: {node: '>=22.0.0'} peerDependencies: eslint: '*' typescript: '*' + peerDependenciesMeta: + eslint: + optional: true - '@eslint-react/core@5.20.8': - resolution: {integrity: sha512-EreVZ4/IQlHv2TwlNuDwvG92S0vWhuYnunnl5HMZOegKUWI59ypGMZkg+/4vec0fbJE56Ux9pFfZQVnpNGuROw==} + '@eslint-react/core@5.23.5': + resolution: {integrity: sha512-aPsmQ6NKxRzDxsEMGQ/rpCTO0JjMMFsZ0XdP6oGGT7maDaizgSp2TN0BxvQp6aEcjJ+4JAHoDptJD4wo+qyb1g==} engines: {node: '>=22.0.0'} peerDependencies: eslint: '*' typescript: '*' + peerDependenciesMeta: + eslint: + optional: true - '@eslint-react/eslint-plugin@5.20.8': - resolution: {integrity: sha512-mcpE/HcgcaiPOuhdmnbpcoEclRjw5L+W0SMj71UDz0+pamXP66nJhQbc0hydeR8lI4Ac90QD9yP2ktruDJaGVA==} + '@eslint-react/eslint-plugin@5.23.5': + resolution: {integrity: sha512-2hpUI1leg4JIxSd1xZ4AEFWmMNZnFYfOOA95Qa7c8kEk+lExpiAG6p5uAucthlgbfRwrimw7Y2lJNoDzFtreJA==} engines: {node: '>=22.0.0'} peerDependencies: eslint: '*' typescript: '*' + peerDependenciesMeta: + eslint: + optional: true - '@eslint-react/eslint@5.20.8': - resolution: {integrity: sha512-d3Vbp5IWcBGlvxNCDPwy6WFyo0r1EONNKGAA7KAnOZVqFvoZ7fuYCAHrFclPeUjgVpP/KJ57uuJbymlrq/oawQ==} + '@eslint-react/eslint@5.23.5': + resolution: {integrity: sha512-gc7tFvf37WOkD9PpdZOdAtu0ZuGAL6TdA98KI6h9r3xmugZbhPv+q0zqNxkgWEtqEQIF9PNgKlUojf4dm09XPQ==} engines: {node: '>=22.0.0'} peerDependencies: eslint: '*' typescript: '*' + peerDependenciesMeta: + eslint: + optional: true - '@eslint-react/jsx@5.20.8': - resolution: {integrity: sha512-eWIPY/j+c5OESPdp8FB+r+EHauRynYRk2MTBKgHoWFz9rduqGY7V0+I6JoL29LU5c/s48O+ZkaRLYVeBB/V+Dg==} + '@eslint-react/jsx@5.23.5': + resolution: {integrity: sha512-EWF+W+eeWqAWemPvl7mJpJkHWpsXPAq/tQVCbZZurEM1gGwJcYr3ieQ1CtBfCqAL3xv9DJ8wobElJoN+8oeylg==} engines: {node: '>=22.0.0'} peerDependencies: eslint: '*' typescript: '*' + peerDependenciesMeta: + eslint: + optional: true - '@eslint-react/shared@5.20.8': - resolution: {integrity: sha512-U32IV1q31lmo7S1zX0HmbUioK3EeW9jpf2t/NKEBRr/C4IfDxNV/Tv2SoqTcxZWcqCnKeEgABrHzH64idlS9Gw==} + '@eslint-react/shared@5.23.5': + resolution: {integrity: sha512-XlGy2IR0Gjmt1LXuzJE/r0Ubf2bUue4J2IIkvCnCRVumNenBksJSDu9xnEEmQTnI589ZB7Md0XRtAyw7cXfNeA==} engines: {node: '>=22.0.0'} peerDependencies: eslint: '*' typescript: '*' + peerDependenciesMeta: + eslint: + optional: true - '@eslint-react/var@5.20.8': - resolution: {integrity: sha512-uGAquJMp3TCaIZZywc2VHuGM2qCDE/Xta1DYvCyyk3qVinR/X9uIXFoHh/SBHeqky5v5Fx8C8nVj40MGa3yRhA==} + '@eslint-react/var@5.23.5': + resolution: {integrity: sha512-ga7bmiqftGMs/BfsWPfnmAvXbknOdxuZSNj7vGRy+t4S+kqbL2ngGJom9exCHzyuK1hSZ/VBfqK4LP4wUv3lkA==} engines: {node: '>=22.0.0'} peerDependencies: eslint: '*' typescript: '*' + peerDependenciesMeta: + eslint: + optional: true '@eslint/config-array@0.23.5': resolution: {integrity: sha512-Y3kKLvC1dvTOT+oGlqNQ1XLqK6D1HU2YXPc52NmAlJZbMMWDzGYXMiPRJ8TYD39muD/OTjlZmNJ4ib7dvSrMBA==} @@ -397,8 +418,8 @@ packages: resolution: {integrity: sha512-MwcE1P+AZ4C6DWlpin/OmOA54mmIZ/+xZuJiQd4SyB29oAJjN30UW9wkKNptW2ctp4cEsvhlLY/CsQ1uoHDloQ==} engines: {node: ^20.19.0 || ^22.13.0 || >=24} - '@eslint/css-tree@4.1.0': - resolution: {integrity: sha512-cg0ohyrAG3swyGqt8t1K/OK97DqBw/ftDvlvyY1fmEst5B40UOmsimwLENq74z2dyw5CDM+3zJIW+CV2nFNDdA==} + '@eslint/css-tree@4.1.1': + resolution: {integrity: sha512-A+s89eP+yDAPkKjnIOfjlkGifg5J7GPn01nWx14UjBVWktfkxOU4975F9ZZ13xNxkGiblNcBaAqgKhnQrW6SWA==} engines: {node: ^20.19.0 || ^22.13.0 || >=24} '@eslint/js@10.0.1': @@ -601,16 +622,16 @@ packages: '@types/react@19.3.0': resolution: {integrity: sha512-N0rFCuH9YoxG9/m61l9MfpJKfmLOVU0em7ipIz6TRgSSkvReLB9vL85GB+yr8Bs5leqpvg96JSwF4ZS1s4viQg==} - '@typescript-eslint/eslint-plugin@8.70.0': - resolution: {integrity: sha512-/v8HZt6RlyIZxB3ntehELOcUcfxKPVGWXnQdJuHRmzrqgF8nQypcC/oxGW+Ot4VGKDq81XugPKxx0n5PBtf9PA==} + '@typescript-eslint/eslint-plugin@8.71.0': + resolution: {integrity: sha512-pqcS9c1HxZTHt7End4nXqd0s5lJrrFzrgCkKFJrsbUnaL6M3+6oBFZaslg6Gjsl3argl2DDRFROnXARaZ2e4Nw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - '@typescript-eslint/parser': ^8.70.0 + '@typescript-eslint/parser': ^8.71.0 eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/parser@8.70.0': - resolution: {integrity: sha512-zYvrmj9Yxd63UGaXw+kdt6A0F0s0qveJyuatIM77bYC2DE4pgmg7a50u8LR7PRtXd0x+h+Tl3eXabGm06SWd3Q==} + '@typescript-eslint/parser@8.71.0': + resolution: {integrity: sha512-CG4nPk1f2zc8yw4pALqHsFYH2hdo+h1T9daSp21+Hnxi9LOE3GT9hAfTKJCBXVNM2GmYs1eMEP615wPoeOgk3A==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 @@ -622,18 +643,34 @@ packages: peerDependencies: typescript: '>=4.8.4 <6.1.0' + '@typescript-eslint/project-service@8.71.0': + resolution: {integrity: sha512-aABjw5rjBacYONVPaPiWOCjJu0vEF4a25iQuodlmQYL1trtLZ0X/y+2Vzl3BKI1odM4LnwLE1oUDXYp1wzx1TQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.1.0' + '@typescript-eslint/scope-manager@8.70.0': resolution: {integrity: sha512-8nP3Kwh5hlgZ4FicGvmznAmJe8UL4sdU8tLukrPaMuQmDuk4Y8xYfzu/aYZW4xT2JCgc7H/TpDI5cGlxcWJSqQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@typescript-eslint/scope-manager@8.71.0': + resolution: {integrity: sha512-gWF0BhUcnjZxSpLE8ngS/59n2SB0J3YqRxvX1+2aoRJk9hNtHSLOV+TcarFiOr5ipXm3yc1QrI4c9YZc8zyCxw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@typescript-eslint/tsconfig-utils@8.70.0': resolution: {integrity: sha512-adnkeeNq9Sq1sUf4+FRVc0KdgYghzsgFpZSQVZVvY0LCuUuN0FnQgyGzCJeC4fW1cdXseBAjU2EOqUIjbNcZUw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/type-utils@8.70.0': - resolution: {integrity: sha512-NUMKIhYVaVIVLnRL9CRt+VVcuLgSHUCpXn4/+K8wql+vdInUzvx8BjUO1oJ7cG9shjFJKtF8F8Hh2kCh3/KBVw==} + '@typescript-eslint/tsconfig-utils@8.71.0': + resolution: {integrity: sha512-Z1UlWHADEK2Mlb9NpWfDeSjqoZ5EyrOv4R3eQpbkzqn/EwaIdOpXXupEA1+0ZIOSJSZZDBHG0BrQyN8zUG6Pwg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.1.0' + + '@typescript-eslint/type-utils@8.71.0': + resolution: {integrity: sha512-i8uO1qbdxeKgRnS5sCRt6On3/nfo2d2DwQe3Yvjx543zLy7r8ySqRuPPiIIXAhS03U0v5NfAFx+rUgxFzKKwNw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 @@ -643,12 +680,22 @@ packages: resolution: {integrity: sha512-asTOIYhDg4zdzOScCyaytrsV3cR6B4ecPQlXw/dJIm7J/MZTtCtfVII9JD8Geh4jTCrK/Xe6cg5UevoleMcoJQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@typescript-eslint/types@8.71.0': + resolution: {integrity: sha512-cJ4OoxPGWvFnBTnSZyaU+qJzGTqPTGJY+gDchj6cRyLRdmIdt4rcsE4twj+zPfrNiWuVi38wijHzShL++Z9atQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@typescript-eslint/typescript-estree@8.70.0': resolution: {integrity: sha512-d9NmHMPEKQ7QCLLm1jI3zmoQBwT5KwFYjXBJ9ymZfKCUU+5rmTRykKAFvH5Qn/ZCds3CEAFS9OC9M/jkl0X2bA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <6.1.0' + '@typescript-eslint/typescript-estree@8.71.0': + resolution: {integrity: sha512-PEEF4G5sLLWAS5BpPrUvms4ySZkiBQQZM4z+3ReI46axK5Vqr/vXBQatJQIZZOYdGyPUAKTtsrWzpqKuU+3DEw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.1.0' + '@typescript-eslint/utils@8.70.0': resolution: {integrity: sha512-oZmtKJz/4fufZ2p3+Cn3ijEojcdfR+1zYDH2xKYrEly0dR/Q/1xUPRCOlKGxod78nWlU2UnDe09GZ3TaknBFGA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -656,10 +703,21 @@ packages: eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 typescript: '>=4.8.4 <6.1.0' + '@typescript-eslint/utils@8.71.0': + resolution: {integrity: sha512-pKR/tEMVrXZG23UFKUn5BQf3zfmfk7KQceI2cGzywZ5nxM5Eu3hEJU1utjWzydtzBbcJAQhHN8iPCxobHpPcZQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.1.0' + '@typescript-eslint/visitor-keys@8.70.0': resolution: {integrity: sha512-BoC8PiO4Hkdo0TVJh9Ntxr5MxPDI7/oFsrygN5ADelFSeXG/qgNuucIGA+L5Z6JpPTE/uRfcTWtscjbUaufepQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@typescript-eslint/visitor-keys@8.71.0': + resolution: {integrity: sha512-8eQ9R218XORK+KLosnf4bu/QsUXvUyVwTbArg7/0NMB1Pu87OJKvj4nhFblkYE8gQV73mW1dx1ptlPCkwRGa7A==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@unrs/resolver-binding-android-arm-eabi@1.12.2': resolution: {integrity: sha512-g5T90pqg1bo/7mytQx6F4iBNC0Wsh9cu+z9veDbFjc7HjpesJFWD7QMS0NGStXM075+7dJPPVvBbpZlnrdpi/w==} cpu: [arm] @@ -780,11 +838,11 @@ packages: cpu: [x64] os: [win32] - '@vitest/coverage-v8@5.0.2': - resolution: {integrity: sha512-3ffHBEi8DOOBLwIGBhOBZbRfYFYWjMUuxZicONdhuFEFEG5AVsMOSrVeuROskqnqpbOmEJwxM2eTVZ9N3a1t+A==} + '@vitest/coverage-v8@5.0.3': + resolution: {integrity: sha512-+klsyz7BvT1vCU28Zkfzms1Ia78XD0V41U3FUtRaa3S+vOr/EXvK1O6BvVD7l1RzEjm6SONR2aybOvDDf9u0mQ==} peerDependencies: - '@vitest/browser': 5.0.2 - vitest: 5.0.2 + '@vitest/browser': 5.0.3 + vitest: 5.0.3 peerDependenciesMeta: '@vitest/browser': optional: true @@ -813,8 +871,8 @@ packages: resolution: {integrity: sha512-1EOLRfsTMnyAr3+kEAsP4o9dhaDlGPpD7H5iLBBeq//YpNB1VIahkPhB+eRp9N2Dkfw8oySROjE3yf9XDeaIkQ==} engines: {node: '>=22'} - '@vitest/mocker@5.0.2': - resolution: {integrity: sha512-Z5FS00Q1SJHkB35xATsmWGdQ5WA1/0MV3CDjqyv7GavHv1OfOj145MNfHOlHk7QLes21dKFDHr8EO2zvL+9WGA==} + '@vitest/mocker@5.0.3': + resolution: {integrity: sha512-T8sWAIbkSyAjkwTcaEc3Iu0o9A27X1/kdXrizhZkGuSKScRQtRzclfAMpOTcGdXCsqxeWlpGy3XjqaW8CpLORg==} peerDependencies: msw: ^2.4.9 vite: ^6.0.0 || ^7.0.0 || ^8.0.0 @@ -824,8 +882,8 @@ packages: vite: optional: true - '@vitest/spy@5.0.2': - resolution: {integrity: sha512-Ijc7T1nT9efNb5LxvjaBrEqw3f/QwUv5EE0nKqZxgqsaV/FxAAZ8baGylA8X/Z2oS4Lp+K74Jr6dTJsDKxJDeg==} + '@vitest/spy@5.0.3': + resolution: {integrity: sha512-XhFysQTB8AZ+P4gMi+Lpo99vg2AZi0qKpaB9yXQl37+CaMEAPO3iH/wGVnSyL5MPERiLezpqTVtrR6UZH5GCXg==} acorn-jsx@5.3.2: resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} @@ -906,6 +964,11 @@ packages: engines: {node: '>=6.0.0'} hasBin: true + baseline-browser-mapping@2.11.27: + resolution: {integrity: sha512-ElY12DaROGuan+lMmZ8Cvo/ZUbXPe7Enc/9VU/b1T3Kp4dwytRcNdR8DoSJN5SNJT/CuvcCA0DHDVmMOCePdRQ==} + engines: {node: '>=6.0.0'} + hasBin: true + brace-expansion@1.1.21: resolution: {integrity: sha512-9zeA+KLZNNzglF2TPKRQEDyx6Yby7daAkuy8MiPzpXPsYDWi/DRM8jmwUDxokQjYqBpv5DgPiwD4h4ZZSy1Ujw==} @@ -922,6 +985,11 @@ packages: engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} hasBin: true + browserslist@4.29.3: + resolution: {integrity: sha512-1R4kiYKXGViqEN0CnoDrXc1StD9niAwu+j2dukWzrD4bJgsD4lDmEp0CRbc6E/vYJIfTHwPmwyaKtVSudICdPA==} + engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} + hasBin: true + builtin-modules@3.3.0: resolution: {integrity: sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==} engines: {node: '>=6'} @@ -952,6 +1020,9 @@ packages: caniuse-lite@1.0.30001810: resolution: {integrity: sha512-TITQPUkaz+aVk5GL6NhOdwk1aEaNTSDPsGFWrTuhKGtjTF70jL/Oht2W4c6rXUe5fu7Ie19VIahAXHIIiWWNeg==} + caniuse-lite@1.0.30001814: + resolution: {integrity: sha512-/Uaf1lAzr59XcMpW0o96WoEfr+VXK2OX4U9AgFoiSHsVJ4HppnIFUjtYzsyDH2+tgANaQb2/oxYGwCPapN1FpA==} + chai@6.2.2: resolution: {integrity: sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==} engines: {node: '>=18'} @@ -1052,6 +1123,9 @@ packages: electron-to-chromium@1.5.429: resolution: {integrity: sha512-/1ENIE3cx4HTIx4IfPZFaOunJmsrSVTnj6coXoRVbiJUbkeTyFkJvBeWGkdgh08OhFbxYLMT1kbkwFVSarq6Ow==} + electron-to-chromium@1.5.444: + resolution: {integrity: sha512-5ss/uJfoDYDHT0lfJzT6FbcskIzROIOPf0BbbFkGcvDzoJU7i//9GDrwwIHQVmIsrAGiF3ihpADBRIsrEFt1rQ==} + emoji-regex@9.2.2: resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} @@ -1175,12 +1249,15 @@ packages: peerDependencies: eslint: '>=8.40.0' - eslint-plugin-react-dom@5.20.8: - resolution: {integrity: sha512-IQYQbC0v+F8FlxAabLrQCXxxD6kDy8YHylQLZ1/E2iO2ufUnjOyAsVhhX9VEGVHvUpHck4CDoHCZr47msQ4pow==} + eslint-plugin-react-dom@5.23.5: + resolution: {integrity: sha512-JWMTlHF9956J7KXPCMbpliNJt+WNEj7gJYird4kksPxlfT/9Q642lWHzuHNzGGvwV+1AIaFtKLeRrgAU6mvMGA==} engines: {node: '>=22.0.0'} peerDependencies: eslint: '*' typescript: '*' + peerDependenciesMeta: + eslint: + optional: true eslint-plugin-react-hooks@7.1.1: resolution: {integrity: sha512-f2I7Gw6JbvCexzIInuSbZpfdQ44D7iqdWX01FKLvrPgqxoE7oMj8clOfto8U6vYiz4yd5oKu39rRSVOe1zRu0g==} @@ -1188,45 +1265,60 @@ packages: peerDependencies: eslint: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0 || ^10.0.0 - eslint-plugin-react-jsx@5.20.8: - resolution: {integrity: sha512-PHRxUYaezGl6E74TvnR4HP+SfIHoKPOUsfaB8gNiFxVmnMmAsEowZlErmHUciorUEQsD582qXTkkMKMBU2qhJA==} + eslint-plugin-react-jsx@5.23.5: + resolution: {integrity: sha512-I9CZz3rTi5HAP9qJJOZE3ZWyOgL2T0NI1jLLM1whc5WVkVFc62qeymaPJka8OndiAbg6RceOWilMQOWufh+uGw==} engines: {node: '>=22.0.0'} peerDependencies: eslint: '*' typescript: '*' + peerDependenciesMeta: + eslint: + optional: true - eslint-plugin-react-naming-convention@5.20.8: - resolution: {integrity: sha512-ECodOVXG7Xp8xVSZeNaFdEPmletWQ49JzIbeAtzDjg54Yn2cnpoxnAuy3eMFJhs8waDc409QoxTrKIX+PSBwnw==} + eslint-plugin-react-naming-convention@5.23.5: + resolution: {integrity: sha512-vcQbwrzKLVHMvLpuzmOIZxsXT0SbORyg12xbz46xdDIKvQfgKlBs9lqlOY7SJidRrEKZUadRtQEhTA76dA2VNQ==} engines: {node: '>=22.0.0'} peerDependencies: eslint: '*' typescript: '*' + peerDependenciesMeta: + eslint: + optional: true eslint-plugin-react-refresh@0.5.7: resolution: {integrity: sha512-XhJSzLljuYD4UjNuFGJu2v7aD3sqNVK12w5/DCUfrNfHWegZo2+TsavNx8MuxMwEquuFAzNbNfXKU0WoZ+YUIg==} peerDependencies: eslint: ^9 || ^10 - eslint-plugin-react-rsc@5.20.8: - resolution: {integrity: sha512-6G6YLzjsB76jZwy9ifYGf7hcE434+xPF8WSilXJ9EZSBpyrfuT6OUktWXTSYLSqVbHTVceZW82DKs/SYfmRa6Q==} + eslint-plugin-react-rsc@5.23.5: + resolution: {integrity: sha512-ZAOiXLFlvFd7T+A/7REe9oMf6XqTKj3pyNgeTvubIDVAMh6JEzibvxwpQABPTKwkMkRgzVagUX4k85UoMA82JQ==} engines: {node: '>=22.0.0'} peerDependencies: eslint: '*' typescript: '*' + peerDependenciesMeta: + eslint: + optional: true - eslint-plugin-react-web-api@5.20.8: - resolution: {integrity: sha512-iTmDL57D3xH2MGFMNyVMYFbaj6d/obAa/2WAN1P0hvOwNfADHw0DYKjV+IcXg0Mke5wWNExNgWsq3ksXfD4ZOw==} + eslint-plugin-react-web-api@5.23.5: + resolution: {integrity: sha512-VsnmZWgpbYfx/qQHpsqshwUdbz7Sw1ME7z3fe0BGOnG0aKe7BrygEd+qgIji7UzyPXkp78Zp850emhFOqkUfuA==} engines: {node: '>=22.0.0'} peerDependencies: eslint: '*' typescript: '*' + peerDependenciesMeta: + eslint: + optional: true - eslint-plugin-react-x@5.20.8: - resolution: {integrity: sha512-G86Iu4K3cDjFTVwQ+24aZ+fu3+2U6zrIFcJ0xCdMVD3OcAPdreLidwDtHB9NLLWTK3dQ4DeM0H/q0d3jar5Rwg==} + eslint-plugin-react-x@5.23.5: + resolution: {integrity: sha512-9R1fmwp7kK1m+RTzGKBa63/hSeTqtQP6uWzURqC/928vqqsQYL0MEcEUuXGkiPHdpG1io7TuJc5U1Qs5MBlZOg==} engines: {node: '>=22.0.0'} peerDependencies: eslint: '*' typescript: '*' + peerDependenciesMeta: + eslint: + optional: true eslint-plugin-react-you-might-not-need-an-effect@1.0.2: resolution: {integrity: sha512-HgYol2zhH3KbnW9Q4FY/FcIINbsYVL6rwQESChqBC2s9SLWtw1wg05+J7SCrr3BfQpNY2ReYhf8xjpd2JhKJOQ==} @@ -1234,13 +1326,13 @@ packages: peerDependencies: eslint: '>=8.40.0' - eslint-plugin-sonarjs@4.2.1: - resolution: {integrity: sha512-WGsnVkUxmHyBOytShcIeE+vhQaS8e+gYgbHNNi4y2e5Zj+DpE2DLTvarqDUhNwjceTZHGLgevSgfBSEVkt4EaA==} + eslint-plugin-sonarjs@4.2.2: + resolution: {integrity: sha512-usNF9GfP89ibwhfxpZaCMW40sC7UEOusDgDCk7lyqAhh4AYdbBaAgLVL5Y/QS0jSed8eJ2ACGH6zzE8ezd/yZQ==} peerDependencies: eslint: ^8.0.0 || ^9.0.0 || ^10.0.0 - eslint-plugin-unicorn@76.0.0: - resolution: {integrity: sha512-3ywJrCMHKYhcBP5yFzGaOOX3dY7+iR3hvpuoS/0EnhRFgVWLejly+t4fOdJvWQxJdHZl1C+QBW7hybXPDld4Pw==} + eslint-plugin-unicorn@77.0.0: + resolution: {integrity: sha512-aayznMCKTXlPAsoyUjRok862sz3BJAfxpQX/t79AT/uVQTmWsCx3FXfXo+9hfhL8aR2mMZQYyXoDZo3/ly6lJw==} engines: {node: '>=22'} peerDependencies: eslint: '>=10.4' @@ -1257,8 +1349,8 @@ packages: resolution: {integrity: sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==} engines: {node: ^20.19.0 || ^22.13.0 || >=24} - eslint@10.11.0: - resolution: {integrity: sha512-P7a6UEEqb9G95MYAtqkmsTbVXIYyzIfl6NGOIJk162PaahFxFyeGcrlXYFSiagECg4sEm8IseJdZBKR3rx6MsQ==} + eslint@10.12.0: + resolution: {integrity: sha512-npHjrHb2o4ehH3mE+YuxABR+0gyb0aVWCIolgJDArwCiHEIsonBm3ZKjel5b0U5+a+MbEvIyRCGCzD3eKKK2NA==} engines: {node: ^20.19.0 || ^22.13.0 || >=24} hasBin: true peerDependencies: @@ -1390,6 +1482,10 @@ packages: resolution: {integrity: sha512-cezEd/DTyyht9cvSSURyygXPfy04GtWO/5e6ZPvH7fCtjKz9PYOmuawphw1Ctd1f6C+5JypXfGD7ahNMXvevBA==} engines: {node: '>=18'} + globals@17.13.0: + resolution: {integrity: sha512-RwMTC61u7hrG3ZJMkZQOK4JLJrKS8QtoTii63EmWvFXHqycY9FObBJQCbsLxSO8kjKhWE5kV1GC+Vb1g3RI0Zg==} + engines: {node: '>=18'} + globalthis@1.0.4: resolution: {integrity: sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==} engines: {node: '>= 0.4'} @@ -1729,8 +1825,8 @@ packages: resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} engines: {node: '>= 0.4'} - mdn-data@2.34.0: - resolution: {integrity: sha512-OgIlLv0NxJKVW4GTSAoEgpRGd4F2XCqGinK0MsMlBCCS/Zcm2/LsbercNWNA7PeMMcjl75NnI97eqyo7zkdxWA==} + mdn-data@2.35.0: + resolution: {integrity: sha512-k+1+dEIm2z/BEfdvYzT/fIyAGeDBo/1AJNJI4ilFXJRYoxP/3Ds+GP8shAOIq8jYXY9N5nGB3Ag104NsagmLAA==} micromatch@4.0.8: resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} @@ -1773,6 +1869,10 @@ packages: resolution: {integrity: sha512-mIrE/Cw9y+9Au6dS5vDKDhQza9YvG6w+ZrS6X+ZzA7yFW/soAeaups4Qzn1bL6g5FVy8WtP79+0j82oPIbqRjQ==} engines: {node: '>=18'} + node-releases@2.0.57: + resolution: {integrity: sha512-kQK9LGGFiHtrWiNhZtA7Qbw17AQz+dmsEKODRIVTXA9+e5MS/2gZEBhYJt13GrAz5/IOZKddH/0Z3TP/Zgo+yw==} + engines: {node: '>=18'} + object-inspect@1.13.4: resolution: {integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==} engines: {node: '>= 0.4'} @@ -1893,8 +1993,8 @@ packages: resolution: {integrity: sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==} engines: {node: '>= 0.4'} - regjsparser@0.13.2: - resolution: {integrity: sha512-NgRBy2Nx/bE+9F27nVHnqcN5HjyLmecqsqx2PJHu3/IEtADD4WuxuXIVExD5PoSDFVrl78dOonfcOe5O+5nbzQ==} + regjsparser@0.13.3: + resolution: {integrity: sha512-ycwFAS14Jw4mppvmK4GR/J6u3WpWpjkEApehuHtLc/8VpPNpDMbQ4WjqwplXifGeyKOzHSFLmSPqzksDQE2Sfg==} hasBin: true reserved-identifiers@1.2.0: @@ -2077,8 +2177,8 @@ packages: resolution: {integrity: sha512-phPGCwqr2+Qo0fwniCE8e4pKnGu/yFb5nD5Y8bf0EEeiI5GklnACYA9GFy/DrAeRrKHXvHn+1SUsOWgJp6RO+g==} engines: {node: '>= 0.4'} - typescript-eslint@8.70.0: - resolution: {integrity: sha512-P/W5cz70/cQAuKfY3xwQMWWTV7BvJ0mAQmi+9mBcsVPaBUpd6Ohpa+fECv9rBFrQcig86jAiNBFNWUqnTjr4pw==} + typescript-eslint@8.71.0: + resolution: {integrity: sha512-fBdHYiqQ14RW6mOMXD14Svn82ZsCYAoQSzGRzyEjR59S5A2Krh/l7fGTOQ7iCr8gGy/mHVXtEF7s5fgjEdV0Pw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 @@ -2153,20 +2253,20 @@ packages: yaml: optional: true - vitest@5.0.2: - resolution: {integrity: sha512-7MQrx9pDv5aHiUcovIb/70Ys3tgtkUVgCtledvKdCmEO+/1Dicq5ZqoSxOW034m03oqC+oHOKui2dM6qtMLoJg==} + vitest@5.0.3: + resolution: {integrity: sha512-xMw97S3rjdtj5dkVat7jCsqWBpvchs3RlpQctUqwJD0KkERk40vz2fJ77lDwW/Vzh/pk18eItYAzkodhSes3jQ==} engines: {node: ^22.12.0 || ^24.0.0 || >=26.0.0} hasBin: true peerDependencies: '@edge-runtime/vm': '*' '@opentelemetry/api': ^1.9.0 '@types/node': ^22.0.0 || >=24.0.0 - '@vitest/browser-playwright': 5.0.2 - '@vitest/browser-preview': 5.0.2 + '@vitest/browser-playwright': 5.0.3 + '@vitest/browser-preview': 5.0.3 '@vitest/browser-webdriverio': ^5.0.0-beta.5 || >=5.0.0 - '@vitest/coverage-istanbul': 5.0.2 - '@vitest/coverage-v8': 5.0.2 - '@vitest/ui': 5.0.2 + '@vitest/coverage-istanbul': 5.0.3 + '@vitest/coverage-v8': 5.0.3 + '@vitest/ui': 5.0.3 happy-dom: '*' jsdom: '*' vite: ^6.4.0 || ^7.0.0 || ^8.0.0 @@ -2215,8 +2315,8 @@ packages: engines: {node: '>= 8'} hasBin: true - why-is-node-running@3.2.2: - resolution: {integrity: sha512-NKUzAelcoCXhXL4dJzKIwXeR8iEVqsA0Lq6Vnd0UXvgaKbzVo4ZTHROF2Jidrv+SgxOQ03fMinnNhzZATxOD3A==} + why-is-node-running@3.2.1: + resolution: {integrity: sha512-Tb2FUhB4vUsGQlfSquQLYkApkuPAFQXGFzxWKHHumVz2dK+X1RUm/HnID4+TfIGYJ1kTcwOaCk/buYCEJr6YjQ==} engines: {node: '>=20.11'} hasBin: true @@ -2352,10 +2452,10 @@ snapshots: '@bcoe/v8-coverage@1.0.2': {} - '@boundaries/elements@3.1.1(@typescript-eslint/parser@8.70.0(eslint@10.11.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3))(eslint-import-resolver-typescript@4.4.5(eslint@10.11.0(supports-color@7.2.0))(supports-color@7.2.0))(eslint@10.11.0(supports-color@7.2.0))(supports-color@7.2.0)': + '@boundaries/elements@3.1.1(@typescript-eslint/parser@8.71.0(eslint@10.12.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3))(eslint-import-resolver-typescript@4.4.5(eslint@10.12.0(supports-color@7.2.0))(supports-color@7.2.0))(eslint@10.12.0(supports-color@7.2.0))(supports-color@7.2.0)': dependencies: eslint-import-resolver-node: 0.3.9(supports-color@7.2.0) - eslint-module-utils: 2.12.1(@typescript-eslint/parser@8.70.0(eslint@10.11.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3))(eslint-import-resolver-node@0.3.9(supports-color@7.2.0))(eslint-import-resolver-typescript@4.4.5(eslint@10.11.0(supports-color@7.2.0))(supports-color@7.2.0))(eslint@10.11.0(supports-color@7.2.0))(supports-color@7.2.0) + eslint-module-utils: 2.12.1(@typescript-eslint/parser@8.71.0(eslint@10.12.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3))(eslint-import-resolver-node@0.3.9(supports-color@7.2.0))(eslint-import-resolver-typescript@4.4.5(eslint@10.12.0(supports-color@7.2.0))(supports-color@7.2.0))(eslint@10.12.0(supports-color@7.2.0))(supports-color@7.2.0) handlebars: 4.7.9 is-core-module: 2.16.1 micromatch: 4.0.8 @@ -2394,96 +2494,101 @@ snapshots: tslib: 2.8.1 optional: true - '@eslint-community/eslint-utils@4.10.1(eslint@10.11.0(supports-color@7.2.0))': + '@eslint-community/eslint-utils@4.10.1(eslint@10.12.0(supports-color@7.2.0))': dependencies: - eslint: 10.11.0(supports-color@7.2.0) + eslint: 10.12.0(supports-color@7.2.0) eslint-visitor-keys: 3.4.3 '@eslint-community/regexpp@4.12.2': {} - '@eslint-react/ast@5.20.8(eslint@10.11.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3)': + '@eslint-react/ast@5.23.5(eslint@10.12.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3)': dependencies: - '@typescript-eslint/types': 8.70.0 - '@typescript-eslint/typescript-estree': 8.70.0(supports-color@7.2.0)(typescript@6.0.3) - '@typescript-eslint/utils': 8.70.0(eslint@10.11.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) - eslint: 10.11.0(supports-color@7.2.0) - string-ts: 2.3.1 + '@typescript-eslint/types': 8.71.0 + '@typescript-eslint/typescript-estree': 8.71.0(supports-color@7.2.0)(typescript@6.0.3) + '@typescript-eslint/utils': 8.71.0(eslint@10.12.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) typescript: 6.0.3 + optionalDependencies: + eslint: 10.12.0(supports-color@7.2.0) transitivePeerDependencies: - supports-color - '@eslint-react/core@5.20.8(eslint@10.11.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3)': + '@eslint-react/core@5.23.5(eslint@10.12.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3)': dependencies: - '@eslint-react/ast': 5.20.8(eslint@10.11.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) - '@eslint-react/eslint': 5.20.8(eslint@10.11.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) - '@eslint-react/shared': 5.20.8(eslint@10.11.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) - '@eslint-react/var': 5.20.8(eslint@10.11.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) - '@typescript-eslint/scope-manager': 8.70.0 - '@typescript-eslint/types': 8.70.0 - '@typescript-eslint/utils': 8.70.0(eslint@10.11.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) - eslint: 10.11.0(supports-color@7.2.0) + '@eslint-react/ast': 5.23.5(eslint@10.12.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) + '@eslint-react/eslint': 5.23.5(eslint@10.12.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) + '@eslint-react/shared': 5.23.5(eslint@10.12.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) + '@eslint-react/var': 5.23.5(eslint@10.12.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) + '@typescript-eslint/types': 8.71.0 + '@typescript-eslint/utils': 8.71.0(eslint@10.12.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) ts-pattern: 5.9.0 typescript: 6.0.3 + optionalDependencies: + eslint: 10.12.0(supports-color@7.2.0) transitivePeerDependencies: - supports-color - '@eslint-react/eslint-plugin@5.20.8(eslint@10.11.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3)': + '@eslint-react/eslint-plugin@5.23.5(eslint@10.12.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3)': dependencies: - '@eslint-react/shared': 5.20.8(eslint@10.11.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) - eslint: 10.11.0(supports-color@7.2.0) - eslint-plugin-react-dom: 5.20.8(eslint@10.11.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) - eslint-plugin-react-jsx: 5.20.8(eslint@10.11.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) - eslint-plugin-react-naming-convention: 5.20.8(eslint@10.11.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) - eslint-plugin-react-rsc: 5.20.8(eslint@10.11.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) - eslint-plugin-react-web-api: 5.20.8(eslint@10.11.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) - eslint-plugin-react-x: 5.20.8(eslint@10.11.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) + '@eslint-react/shared': 5.23.5(eslint@10.12.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) + eslint-plugin-react-dom: 5.23.5(eslint@10.12.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) + eslint-plugin-react-jsx: 5.23.5(eslint@10.12.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) + eslint-plugin-react-naming-convention: 5.23.5(eslint@10.12.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) + eslint-plugin-react-rsc: 5.23.5(eslint@10.12.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) + eslint-plugin-react-web-api: 5.23.5(eslint@10.12.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) + eslint-plugin-react-x: 5.23.5(eslint@10.12.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) typescript: 6.0.3 + optionalDependencies: + eslint: 10.12.0(supports-color@7.2.0) transitivePeerDependencies: - supports-color - '@eslint-react/eslint@5.20.8(eslint@10.11.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3)': + '@eslint-react/eslint@5.23.5(eslint@10.12.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3)': dependencies: - '@typescript-eslint/utils': 8.70.0(eslint@10.11.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) - eslint: 10.11.0(supports-color@7.2.0) + '@typescript-eslint/types': 8.71.0 + '@typescript-eslint/utils': 8.71.0(eslint@10.12.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) typescript: 6.0.3 + optionalDependencies: + eslint: 10.12.0(supports-color@7.2.0) transitivePeerDependencies: - supports-color - '@eslint-react/jsx@5.20.8(eslint@10.11.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3)': + '@eslint-react/jsx@5.23.5(eslint@10.12.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3)': dependencies: - '@eslint-react/ast': 5.20.8(eslint@10.11.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) - '@eslint-react/eslint': 5.20.8(eslint@10.11.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) - '@eslint-react/shared': 5.20.8(eslint@10.11.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) - '@eslint-react/var': 5.20.8(eslint@10.11.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) - '@typescript-eslint/types': 8.70.0 - '@typescript-eslint/utils': 8.70.0(eslint@10.11.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) - eslint: 10.11.0(supports-color@7.2.0) + '@eslint-react/ast': 5.23.5(eslint@10.12.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) + '@eslint-react/eslint': 5.23.5(eslint@10.12.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) + '@eslint-react/var': 5.23.5(eslint@10.12.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) + '@typescript-eslint/types': 8.71.0 + '@typescript-eslint/utils': 8.71.0(eslint@10.12.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) ts-pattern: 5.9.0 typescript: 6.0.3 + optionalDependencies: + eslint: 10.12.0(supports-color@7.2.0) transitivePeerDependencies: - supports-color - '@eslint-react/shared@5.20.8(eslint@10.11.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3)': + '@eslint-react/shared@5.23.5(eslint@10.12.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3)': dependencies: - '@eslint-react/eslint': 5.20.8(eslint@10.11.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) - '@typescript-eslint/utils': 8.70.0(eslint@10.11.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) - eslint: 10.11.0(supports-color@7.2.0) + '@eslint-react/eslint': 5.23.5(eslint@10.12.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) + '@typescript-eslint/utils': 8.71.0(eslint@10.12.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) ts-pattern: 5.9.0 typescript: 6.0.3 zod: 4.6.5 + optionalDependencies: + eslint: 10.12.0(supports-color@7.2.0) transitivePeerDependencies: - supports-color - '@eslint-react/var@5.20.8(eslint@10.11.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3)': + '@eslint-react/var@5.23.5(eslint@10.12.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3)': dependencies: - '@eslint-react/ast': 5.20.8(eslint@10.11.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) - '@eslint-react/eslint': 5.20.8(eslint@10.11.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) - '@typescript-eslint/scope-manager': 8.70.0 - '@typescript-eslint/types': 8.70.0 - '@typescript-eslint/utils': 8.70.0(eslint@10.11.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) - eslint: 10.11.0(supports-color@7.2.0) + '@eslint-react/ast': 5.23.5(eslint@10.12.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) + '@eslint-react/eslint': 5.23.5(eslint@10.12.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) + '@typescript-eslint/scope-manager': 8.71.0 + '@typescript-eslint/types': 8.71.0 + '@typescript-eslint/utils': 8.71.0(eslint@10.12.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) ts-pattern: 5.9.0 typescript: 6.0.3 + optionalDependencies: + eslint: 10.12.0(supports-color@7.2.0) transitivePeerDependencies: - supports-color @@ -2503,14 +2608,14 @@ snapshots: dependencies: '@types/json-schema': 7.0.15 - '@eslint/css-tree@4.1.0': + '@eslint/css-tree@4.1.1': dependencies: - mdn-data: 2.34.0 + mdn-data: 2.35.0 source-map-js: 1.2.1 - '@eslint/js@10.0.1(eslint@10.11.0(supports-color@7.2.0))': + '@eslint/js@10.0.1(eslint@10.12.0(supports-color@7.2.0))': optionalDependencies: - eslint: 10.11.0(supports-color@7.2.0) + eslint: 10.12.0(supports-color@7.2.0) '@eslint/object-schema@3.0.5': {} @@ -2646,15 +2751,15 @@ snapshots: dependencies: csstype: 3.2.3 - '@typescript-eslint/eslint-plugin@8.70.0(@typescript-eslint/parser@8.70.0(eslint@10.11.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3))(eslint@10.11.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3)': + '@typescript-eslint/eslint-plugin@8.71.0(@typescript-eslint/parser@8.71.0(eslint@10.12.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3))(eslint@10.12.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3)': dependencies: '@eslint-community/regexpp': 4.12.2 - '@typescript-eslint/parser': 8.70.0(eslint@10.11.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) - '@typescript-eslint/scope-manager': 8.70.0 - '@typescript-eslint/type-utils': 8.70.0(eslint@10.11.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) - '@typescript-eslint/utils': 8.70.0(eslint@10.11.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) - '@typescript-eslint/visitor-keys': 8.70.0 - eslint: 10.11.0(supports-color@7.2.0) + '@typescript-eslint/parser': 8.71.0(eslint@10.12.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) + '@typescript-eslint/scope-manager': 8.71.0 + '@typescript-eslint/type-utils': 8.71.0(eslint@10.12.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) + '@typescript-eslint/utils': 8.71.0(eslint@10.12.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) + '@typescript-eslint/visitor-keys': 8.71.0 + eslint: 10.12.0(supports-color@7.2.0) ignore: 7.0.9 natural-compare: 1.4.0 ts-api-utils: 2.5.0(typescript@6.0.3) @@ -2662,14 +2767,14 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/parser@8.70.0(eslint@10.11.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3)': + '@typescript-eslint/parser@8.71.0(eslint@10.12.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3)': dependencies: - '@typescript-eslint/scope-manager': 8.70.0 - '@typescript-eslint/types': 8.70.0 - '@typescript-eslint/typescript-estree': 8.70.0(supports-color@7.2.0)(typescript@6.0.3) - '@typescript-eslint/visitor-keys': 8.70.0 + '@typescript-eslint/scope-manager': 8.71.0 + '@typescript-eslint/types': 8.71.0 + '@typescript-eslint/typescript-estree': 8.71.0(supports-color@7.2.0)(typescript@6.0.3) + '@typescript-eslint/visitor-keys': 8.71.0 debug: 4.4.3(supports-color@7.2.0) - eslint: 10.11.0(supports-color@7.2.0) + eslint: 10.12.0(supports-color@7.2.0) typescript: 6.0.3 transitivePeerDependencies: - supports-color @@ -2683,22 +2788,40 @@ snapshots: transitivePeerDependencies: - supports-color + '@typescript-eslint/project-service@8.71.0(supports-color@7.2.0)(typescript@6.0.3)': + dependencies: + '@typescript-eslint/tsconfig-utils': 8.71.0(typescript@6.0.3) + '@typescript-eslint/types': 8.71.0 + debug: 4.4.3(supports-color@7.2.0) + typescript: 6.0.3 + transitivePeerDependencies: + - supports-color + '@typescript-eslint/scope-manager@8.70.0': dependencies: '@typescript-eslint/types': 8.70.0 '@typescript-eslint/visitor-keys': 8.70.0 + '@typescript-eslint/scope-manager@8.71.0': + dependencies: + '@typescript-eslint/types': 8.71.0 + '@typescript-eslint/visitor-keys': 8.71.0 + '@typescript-eslint/tsconfig-utils@8.70.0(typescript@6.0.3)': dependencies: typescript: 6.0.3 - '@typescript-eslint/type-utils@8.70.0(eslint@10.11.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3)': + '@typescript-eslint/tsconfig-utils@8.71.0(typescript@6.0.3)': dependencies: - '@typescript-eslint/types': 8.70.0 - '@typescript-eslint/typescript-estree': 8.70.0(supports-color@7.2.0)(typescript@6.0.3) - '@typescript-eslint/utils': 8.70.0(eslint@10.11.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) + typescript: 6.0.3 + + '@typescript-eslint/type-utils@8.71.0(eslint@10.12.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3)': + dependencies: + '@typescript-eslint/types': 8.71.0 + '@typescript-eslint/typescript-estree': 8.71.0(supports-color@7.2.0)(typescript@6.0.3) + '@typescript-eslint/utils': 8.71.0(eslint@10.12.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) debug: 4.4.3(supports-color@7.2.0) - eslint: 10.11.0(supports-color@7.2.0) + eslint: 10.12.0(supports-color@7.2.0) ts-api-utils: 2.5.0(typescript@6.0.3) typescript: 6.0.3 transitivePeerDependencies: @@ -2706,6 +2829,8 @@ snapshots: '@typescript-eslint/types@8.70.0': {} + '@typescript-eslint/types@8.71.0': {} + '@typescript-eslint/typescript-estree@8.70.0(supports-color@7.2.0)(typescript@6.0.3)': dependencies: '@typescript-eslint/project-service': 8.70.0(supports-color@7.2.0)(typescript@6.0.3) @@ -2721,13 +2846,39 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/utils@8.70.0(eslint@10.11.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3)': + '@typescript-eslint/typescript-estree@8.71.0(supports-color@7.2.0)(typescript@6.0.3)': dependencies: - '@eslint-community/eslint-utils': 4.10.1(eslint@10.11.0(supports-color@7.2.0)) + '@typescript-eslint/project-service': 8.71.0(supports-color@7.2.0)(typescript@6.0.3) + '@typescript-eslint/tsconfig-utils': 8.71.0(typescript@6.0.3) + '@typescript-eslint/types': 8.71.0 + '@typescript-eslint/visitor-keys': 8.71.0 + debug: 4.4.3(supports-color@7.2.0) + minimatch: 10.2.6 + semver: 7.8.5 + tinyglobby: 0.2.17 + ts-api-utils: 2.5.0(typescript@6.0.3) + typescript: 6.0.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/utils@8.70.0(eslint@10.12.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3)': + dependencies: + '@eslint-community/eslint-utils': 4.10.1(eslint@10.12.0(supports-color@7.2.0)) '@typescript-eslint/scope-manager': 8.70.0 '@typescript-eslint/types': 8.70.0 '@typescript-eslint/typescript-estree': 8.70.0(supports-color@7.2.0)(typescript@6.0.3) - eslint: 10.11.0(supports-color@7.2.0) + eslint: 10.12.0(supports-color@7.2.0) + typescript: 6.0.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/utils@8.71.0(eslint@10.12.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3)': + dependencies: + '@eslint-community/eslint-utils': 4.10.1(eslint@10.12.0(supports-color@7.2.0)) + '@typescript-eslint/scope-manager': 8.71.0 + '@typescript-eslint/types': 8.71.0 + '@typescript-eslint/typescript-estree': 8.71.0(supports-color@7.2.0)(typescript@6.0.3) + eslint: 10.12.0(supports-color@7.2.0) typescript: 6.0.3 transitivePeerDependencies: - supports-color @@ -2737,6 +2888,11 @@ snapshots: '@typescript-eslint/types': 8.70.0 eslint-visitor-keys: 5.0.1 + '@typescript-eslint/visitor-keys@8.71.0': + dependencies: + '@typescript-eslint/types': 8.71.0 + eslint-visitor-keys: 5.0.1 + '@unrs/resolver-binding-android-arm-eabi@1.12.2': optional: true @@ -2807,7 +2963,7 @@ snapshots: '@unrs/resolver-binding-win32-x64-msvc@1.12.2': optional: true - '@vitest/coverage-v8@5.0.2(vitest@5.0.2)': + '@vitest/coverage-v8@5.0.3(vitest@5.0.3)': dependencies: '@bcoe/v8-coverage': 1.0.2 '@vitest/istanbul-lib-coverage': 1.0.1 @@ -2817,17 +2973,17 @@ snapshots: obug: 2.2.1 std-env: 4.2.0 tinyrainbow: 3.1.1 - vitest: 5.0.2(@vitest/coverage-v8@5.0.2)(vite@8.3.0(yaml@2.9.1)) + vitest: 5.0.3(@vitest/coverage-v8@5.0.3)(vite@8.3.0(yaml@2.9.1)) - '@vitest/eslint-plugin@1.6.27(@typescript-eslint/eslint-plugin@8.70.0(@typescript-eslint/parser@8.70.0(eslint@10.11.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3))(eslint@10.11.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3))(eslint@10.11.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3)(vitest@5.0.2)': + '@vitest/eslint-plugin@1.6.27(@typescript-eslint/eslint-plugin@8.71.0(@typescript-eslint/parser@8.71.0(eslint@10.12.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3))(eslint@10.12.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3))(eslint@10.12.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3)(vitest@5.0.3)': dependencies: '@typescript-eslint/scope-manager': 8.70.0 - '@typescript-eslint/utils': 8.70.0(eslint@10.11.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) - eslint: 10.11.0(supports-color@7.2.0) + '@typescript-eslint/utils': 8.70.0(eslint@10.12.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) + eslint: 10.12.0(supports-color@7.2.0) optionalDependencies: - '@typescript-eslint/eslint-plugin': 8.70.0(@typescript-eslint/parser@8.70.0(eslint@10.11.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3))(eslint@10.11.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) + '@typescript-eslint/eslint-plugin': 8.71.0(@typescript-eslint/parser@8.71.0(eslint@10.12.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3))(eslint@10.12.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) typescript: 6.0.3 - vitest: 5.0.2(@vitest/coverage-v8@5.0.2)(vite@8.3.0(yaml@2.9.1)) + vitest: 5.0.3(@vitest/coverage-v8@5.0.3)(vite@8.3.0(yaml@2.9.1)) transitivePeerDependencies: - supports-color @@ -2837,16 +2993,16 @@ snapshots: dependencies: '@vitest/istanbul-lib-coverage': 1.0.1 - '@vitest/mocker@5.0.2(vite@8.3.0(yaml@2.9.1))': + '@vitest/mocker@5.0.3(vite@8.3.0(yaml@2.9.1))': dependencies: '@jridgewell/trace-mapping': 0.3.31 - '@vitest/spy': 5.0.2 + '@vitest/spy': 5.0.3 estree-walker: 3.0.3 magic-string: 1.4.1 optionalDependencies: vite: 8.3.0(yaml@2.9.1) - '@vitest/spy@5.0.2': {} + '@vitest/spy@5.0.3': {} acorn-jsx@5.3.2(acorn@8.18.0): dependencies: @@ -2933,6 +3089,8 @@ snapshots: baseline-browser-mapping@2.11.24: {} + baseline-browser-mapping@2.11.27: {} + brace-expansion@1.1.21: dependencies: balanced-match: 1.0.2 @@ -2954,6 +3112,14 @@ snapshots: node-releases: 2.0.55 update-browserslist-db: 1.3.3(browserslist@4.29.0) + browserslist@4.29.3: + dependencies: + baseline-browser-mapping: 2.11.27 + caniuse-lite: 1.0.30001814 + electron-to-chromium: 1.5.444 + node-releases: 2.0.57 + update-browserslist-db: 1.3.3(browserslist@4.29.3) + builtin-modules@3.3.0: {} builtin-modules@5.3.0: {} @@ -2987,6 +3153,8 @@ snapshots: caniuse-lite@1.0.30001810: {} + caniuse-lite@1.0.30001814: {} + chai@6.2.2: {} chalk@4.1.2: @@ -3080,6 +3248,8 @@ snapshots: electron-to-chromium@1.5.429: {} + electron-to-chromium@1.5.444: {} + emoji-regex@9.2.2: {} entities@8.1.0: {} @@ -3197,10 +3367,10 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-import-resolver-typescript@4.4.5(eslint@10.11.0(supports-color@7.2.0))(supports-color@7.2.0): + eslint-import-resolver-typescript@4.4.5(eslint@10.12.0(supports-color@7.2.0))(supports-color@7.2.0): dependencies: debug: 4.4.3(supports-color@7.2.0) - eslint: 10.11.0(supports-color@7.2.0) + eslint: 10.12.0(supports-color@7.2.0) eslint-import-context: 0.1.9(unrs-resolver@1.12.2) get-tsconfig: 4.14.3 is-bun-module: 2.0.0 @@ -3210,24 +3380,24 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-module-utils@2.12.1(@typescript-eslint/parser@8.70.0(eslint@10.11.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3))(eslint-import-resolver-node@0.3.9(supports-color@7.2.0))(eslint-import-resolver-typescript@4.4.5(eslint@10.11.0(supports-color@7.2.0))(supports-color@7.2.0))(eslint@10.11.0(supports-color@7.2.0))(supports-color@7.2.0): + eslint-module-utils@2.12.1(@typescript-eslint/parser@8.71.0(eslint@10.12.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3))(eslint-import-resolver-node@0.3.9(supports-color@7.2.0))(eslint-import-resolver-typescript@4.4.5(eslint@10.12.0(supports-color@7.2.0))(supports-color@7.2.0))(eslint@10.12.0(supports-color@7.2.0))(supports-color@7.2.0): dependencies: debug: 3.2.7(supports-color@7.2.0) optionalDependencies: - '@typescript-eslint/parser': 8.70.0(eslint@10.11.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) - eslint: 10.11.0(supports-color@7.2.0) + '@typescript-eslint/parser': 8.71.0(eslint@10.12.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) + eslint: 10.12.0(supports-color@7.2.0) eslint-import-resolver-node: 0.3.9(supports-color@7.2.0) - eslint-import-resolver-typescript: 4.4.5(eslint@10.11.0(supports-color@7.2.0))(supports-color@7.2.0) + eslint-import-resolver-typescript: 4.4.5(eslint@10.12.0(supports-color@7.2.0))(supports-color@7.2.0) transitivePeerDependencies: - supports-color - eslint-plugin-boundaries@7.2.0(@typescript-eslint/parser@8.70.0(eslint@10.11.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3))(eslint-import-resolver-typescript@4.4.5(eslint@10.11.0(supports-color@7.2.0))(supports-color@7.2.0))(eslint@10.11.0(supports-color@7.2.0))(supports-color@7.2.0): + eslint-plugin-boundaries@7.2.0(@typescript-eslint/parser@8.71.0(eslint@10.12.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3))(eslint-import-resolver-typescript@4.4.5(eslint@10.12.0(supports-color@7.2.0))(supports-color@7.2.0))(eslint@10.12.0(supports-color@7.2.0))(supports-color@7.2.0): dependencies: - '@boundaries/elements': 3.1.1(@typescript-eslint/parser@8.70.0(eslint@10.11.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3))(eslint-import-resolver-typescript@4.4.5(eslint@10.11.0(supports-color@7.2.0))(supports-color@7.2.0))(eslint@10.11.0(supports-color@7.2.0))(supports-color@7.2.0) + '@boundaries/elements': 3.1.1(@typescript-eslint/parser@8.71.0(eslint@10.12.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3))(eslint-import-resolver-typescript@4.4.5(eslint@10.12.0(supports-color@7.2.0))(supports-color@7.2.0))(eslint@10.12.0(supports-color@7.2.0))(supports-color@7.2.0) chalk: 4.1.2 - eslint: 10.11.0(supports-color@7.2.0) + eslint: 10.12.0(supports-color@7.2.0) eslint-import-resolver-node: 0.3.9(supports-color@7.2.0) - eslint-module-utils: 2.12.1(@typescript-eslint/parser@8.70.0(eslint@10.11.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3))(eslint-import-resolver-node@0.3.9(supports-color@7.2.0))(eslint-import-resolver-typescript@4.4.5(eslint@10.11.0(supports-color@7.2.0))(supports-color@7.2.0))(eslint@10.11.0(supports-color@7.2.0))(supports-color@7.2.0) + eslint-module-utils: 2.12.1(@typescript-eslint/parser@8.71.0(eslint@10.12.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3))(eslint-import-resolver-node@0.3.9(supports-color@7.2.0))(eslint-import-resolver-typescript@4.4.5(eslint@10.12.0(supports-color@7.2.0))(supports-color@7.2.0))(eslint@10.12.0(supports-color@7.2.0))(supports-color@7.2.0) handlebars: 4.7.9 micromatch: 4.0.8 transitivePeerDependencies: @@ -3236,7 +3406,7 @@ snapshots: - eslint-import-resolver-webpack - supports-color - eslint-plugin-jsx-a11y@6.10.2(eslint@10.11.0(supports-color@7.2.0)): + eslint-plugin-jsx-a11y@6.10.2(eslint@10.12.0(supports-color@7.2.0)): dependencies: aria-query: 5.3.2 array-includes: 3.2.0 @@ -3246,7 +3416,7 @@ snapshots: axobject-query: 4.1.0 damerau-levenshtein: 1.0.8 emoji-regex: 9.2.2 - eslint: 10.11.0(supports-color@7.2.0) + eslint: 10.12.0(supports-color@7.2.0) hasown: 2.0.4 jsx-ast-utils: 3.3.5 language-tags: 1.0.9 @@ -3255,147 +3425,152 @@ snapshots: safe-regex-test: 1.1.0 string.prototype.includes: 2.0.1 - eslint-plugin-perfectionist@5.12.1(eslint@10.11.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3): + eslint-plugin-perfectionist@5.12.1(eslint@10.12.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3): dependencies: '@typescript-eslint/types': 8.70.0 - '@typescript-eslint/utils': 8.70.0(eslint@10.11.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) + '@typescript-eslint/utils': 8.70.0(eslint@10.12.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) natural-orderby: 5.0.0 optionalDependencies: - eslint: 10.11.0(supports-color@7.2.0) + eslint: 10.12.0(supports-color@7.2.0) transitivePeerDependencies: - supports-color - typescript - eslint-plugin-playwright@2.12.0(eslint@10.11.0(supports-color@7.2.0)): + eslint-plugin-playwright@2.12.0(eslint@10.12.0(supports-color@7.2.0)): dependencies: - eslint: 10.11.0(supports-color@7.2.0) - globals: 17.12.0 + eslint: 10.12.0(supports-color@7.2.0) + globals: 17.13.0 - eslint-plugin-react-dom@5.20.8(eslint@10.11.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3): + eslint-plugin-react-dom@5.23.5(eslint@10.12.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3): dependencies: - '@eslint-react/ast': 5.20.8(eslint@10.11.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) - '@eslint-react/core': 5.20.8(eslint@10.11.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) - '@eslint-react/eslint': 5.20.8(eslint@10.11.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) - '@eslint-react/jsx': 5.20.8(eslint@10.11.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) - '@eslint-react/shared': 5.20.8(eslint@10.11.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) - '@eslint-react/var': 5.20.8(eslint@10.11.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) - '@typescript-eslint/types': 8.70.0 - '@typescript-eslint/utils': 8.70.0(eslint@10.11.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) + '@eslint-react/ast': 5.23.5(eslint@10.12.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) + '@eslint-react/eslint': 5.23.5(eslint@10.12.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) + '@eslint-react/jsx': 5.23.5(eslint@10.12.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) + '@eslint-react/shared': 5.23.5(eslint@10.12.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) + '@eslint-react/var': 5.23.5(eslint@10.12.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) + '@typescript-eslint/types': 8.71.0 + '@typescript-eslint/utils': 8.71.0(eslint@10.12.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) compare-versions: 6.1.1 - eslint: 10.11.0(supports-color@7.2.0) + ts-pattern: 5.9.0 typescript: 6.0.3 + optionalDependencies: + eslint: 10.12.0(supports-color@7.2.0) transitivePeerDependencies: - supports-color - eslint-plugin-react-hooks@7.1.1(eslint@10.11.0(supports-color@7.2.0))(supports-color@7.2.0): + eslint-plugin-react-hooks@7.1.1(eslint@10.12.0(supports-color@7.2.0))(supports-color@7.2.0): dependencies: '@babel/core': 7.29.7(supports-color@7.2.0) '@babel/parser': 7.29.8 - eslint: 10.11.0(supports-color@7.2.0) + eslint: 10.12.0(supports-color@7.2.0) hermes-parser: 0.25.1 zod: 4.6.5 zod-validation-error: 4.0.2(zod@4.6.5) transitivePeerDependencies: - supports-color - eslint-plugin-react-jsx@5.20.8(eslint@10.11.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3): + eslint-plugin-react-jsx@5.23.5(eslint@10.12.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3): dependencies: - '@eslint-react/ast': 5.20.8(eslint@10.11.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) - '@eslint-react/core': 5.20.8(eslint@10.11.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) - '@eslint-react/eslint': 5.20.8(eslint@10.11.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) - '@eslint-react/jsx': 5.20.8(eslint@10.11.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) - '@eslint-react/shared': 5.20.8(eslint@10.11.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) - '@typescript-eslint/types': 8.70.0 - '@typescript-eslint/utils': 8.70.0(eslint@10.11.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) - eslint: 10.11.0(supports-color@7.2.0) + '@eslint-react/ast': 5.23.5(eslint@10.12.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) + '@eslint-react/core': 5.23.5(eslint@10.12.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) + '@eslint-react/eslint': 5.23.5(eslint@10.12.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) + '@eslint-react/jsx': 5.23.5(eslint@10.12.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) + '@eslint-react/shared': 5.23.5(eslint@10.12.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) + '@typescript-eslint/types': 8.71.0 + '@typescript-eslint/utils': 8.71.0(eslint@10.12.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) typescript: 6.0.3 + optionalDependencies: + eslint: 10.12.0(supports-color@7.2.0) transitivePeerDependencies: - supports-color - eslint-plugin-react-naming-convention@5.20.8(eslint@10.11.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3): + eslint-plugin-react-naming-convention@5.23.5(eslint@10.12.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3): dependencies: - '@eslint-react/ast': 5.20.8(eslint@10.11.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) - '@eslint-react/core': 5.20.8(eslint@10.11.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) - '@eslint-react/eslint': 5.20.8(eslint@10.11.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) - '@eslint-react/shared': 5.20.8(eslint@10.11.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) - '@eslint-react/var': 5.20.8(eslint@10.11.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) - '@typescript-eslint/types': 8.70.0 - '@typescript-eslint/utils': 8.70.0(eslint@10.11.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) - eslint: 10.11.0(supports-color@7.2.0) + '@eslint-react/ast': 5.23.5(eslint@10.12.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) + '@eslint-react/core': 5.23.5(eslint@10.12.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) + '@eslint-react/eslint': 5.23.5(eslint@10.12.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) + '@eslint-react/shared': 5.23.5(eslint@10.12.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) + '@eslint-react/var': 5.23.5(eslint@10.12.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) + '@typescript-eslint/types': 8.71.0 + '@typescript-eslint/utils': 8.71.0(eslint@10.12.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) ts-pattern: 5.9.0 typescript: 6.0.3 + optionalDependencies: + eslint: 10.12.0(supports-color@7.2.0) transitivePeerDependencies: - supports-color - eslint-plugin-react-refresh@0.5.7(eslint@10.11.0(supports-color@7.2.0)): + eslint-plugin-react-refresh@0.5.7(eslint@10.12.0(supports-color@7.2.0)): dependencies: - eslint: 10.11.0(supports-color@7.2.0) + eslint: 10.12.0(supports-color@7.2.0) - eslint-plugin-react-rsc@5.20.8(eslint@10.11.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3): + eslint-plugin-react-rsc@5.23.5(eslint@10.12.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3): dependencies: - '@eslint-react/ast': 5.20.8(eslint@10.11.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) - '@eslint-react/core': 5.20.8(eslint@10.11.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) - '@eslint-react/eslint': 5.20.8(eslint@10.11.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) - '@eslint-react/shared': 5.20.8(eslint@10.11.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) - '@eslint-react/var': 5.20.8(eslint@10.11.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) - '@typescript-eslint/types': 8.70.0 - '@typescript-eslint/utils': 8.70.0(eslint@10.11.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) - eslint: 10.11.0(supports-color@7.2.0) + '@eslint-react/ast': 5.23.5(eslint@10.12.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) + '@eslint-react/core': 5.23.5(eslint@10.12.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) + '@eslint-react/eslint': 5.23.5(eslint@10.12.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) + '@eslint-react/shared': 5.23.5(eslint@10.12.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) + '@eslint-react/var': 5.23.5(eslint@10.12.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) + '@typescript-eslint/types': 8.71.0 + '@typescript-eslint/utils': 8.71.0(eslint@10.12.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) typescript: 6.0.3 + optionalDependencies: + eslint: 10.12.0(supports-color@7.2.0) transitivePeerDependencies: - supports-color - eslint-plugin-react-web-api@5.20.8(eslint@10.11.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3): + eslint-plugin-react-web-api@5.23.5(eslint@10.12.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3): dependencies: - '@eslint-react/ast': 5.20.8(eslint@10.11.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) - '@eslint-react/core': 5.20.8(eslint@10.11.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) - '@eslint-react/eslint': 5.20.8(eslint@10.11.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) - '@eslint-react/shared': 5.20.8(eslint@10.11.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) - '@eslint-react/var': 5.20.8(eslint@10.11.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) - '@typescript-eslint/types': 8.70.0 - '@typescript-eslint/utils': 8.70.0(eslint@10.11.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) - eslint: 10.11.0(supports-color@7.2.0) + '@eslint-react/ast': 5.23.5(eslint@10.12.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) + '@eslint-react/core': 5.23.5(eslint@10.12.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) + '@eslint-react/eslint': 5.23.5(eslint@10.12.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) + '@eslint-react/shared': 5.23.5(eslint@10.12.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) + '@eslint-react/var': 5.23.5(eslint@10.12.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) + '@typescript-eslint/types': 8.71.0 + '@typescript-eslint/utils': 8.71.0(eslint@10.12.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) ts-pattern: 5.9.0 typescript: 6.0.3 + optionalDependencies: + eslint: 10.12.0(supports-color@7.2.0) transitivePeerDependencies: - supports-color - eslint-plugin-react-x@5.20.8(eslint@10.11.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3): + eslint-plugin-react-x@5.23.5(eslint@10.12.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3): dependencies: - '@eslint-react/ast': 5.20.8(eslint@10.11.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) - '@eslint-react/core': 5.20.8(eslint@10.11.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) - '@eslint-react/eslint': 5.20.8(eslint@10.11.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) - '@eslint-react/jsx': 5.20.8(eslint@10.11.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) - '@eslint-react/shared': 5.20.8(eslint@10.11.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) - '@eslint-react/var': 5.20.8(eslint@10.11.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) - '@typescript-eslint/scope-manager': 8.70.0 - '@typescript-eslint/type-utils': 8.70.0(eslint@10.11.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) - '@typescript-eslint/types': 8.70.0 - '@typescript-eslint/typescript-estree': 8.70.0(supports-color@7.2.0)(typescript@6.0.3) - '@typescript-eslint/utils': 8.70.0(eslint@10.11.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) + '@eslint-react/ast': 5.23.5(eslint@10.12.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) + '@eslint-react/core': 5.23.5(eslint@10.12.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) + '@eslint-react/eslint': 5.23.5(eslint@10.12.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) + '@eslint-react/jsx': 5.23.5(eslint@10.12.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) + '@eslint-react/shared': 5.23.5(eslint@10.12.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) + '@eslint-react/var': 5.23.5(eslint@10.12.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) + '@typescript-eslint/scope-manager': 8.71.0 + '@typescript-eslint/types': 8.71.0 + '@typescript-eslint/typescript-estree': 8.71.0(supports-color@7.2.0)(typescript@6.0.3) + '@typescript-eslint/utils': 8.71.0(eslint@10.12.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) compare-versions: 6.1.1 - eslint: 10.11.0(supports-color@7.2.0) string-ts: 2.3.1 ts-api-utils: 2.5.0(typescript@6.0.3) ts-pattern: 5.9.0 typescript: 6.0.3 + optionalDependencies: + eslint: 10.12.0(supports-color@7.2.0) transitivePeerDependencies: - supports-color - eslint-plugin-react-you-might-not-need-an-effect@1.0.2(eslint@10.11.0(supports-color@7.2.0)): + eslint-plugin-react-you-might-not-need-an-effect@1.0.2(eslint@10.12.0(supports-color@7.2.0)): dependencies: - eslint: 10.11.0(supports-color@7.2.0) + eslint: 10.12.0(supports-color@7.2.0) globals: 16.5.0 - eslint-plugin-sonarjs@4.2.1(eslint@10.11.0(supports-color@7.2.0)): + eslint-plugin-sonarjs@4.2.2(eslint@10.12.0(supports-color@7.2.0)): dependencies: '@eslint-community/regexpp': 4.12.2 '@sonarsource/analyzer-commons-configurations': 2.31.0-5284 builtin-modules: 3.3.0 bytes: 3.1.2 - eslint: 10.11.0(supports-color@7.2.0) + eslint: 10.12.0(supports-color@7.2.0) functional-red-black-tree: 1.0.1 - globals: 17.12.0 + globals: 17.13.0 jsx-ast-utils-x: 0.1.0 lodash.merge: 4.6.2 minimatch: 10.2.6 @@ -3405,17 +3580,17 @@ snapshots: typescript: 6.0.3 yaml: 2.9.1 - eslint-plugin-unicorn@76.0.0(eslint@10.11.0(supports-color@7.2.0)): + eslint-plugin-unicorn@77.0.0(eslint@10.12.0(supports-color@7.2.0)): dependencies: - '@eslint-community/eslint-utils': 4.10.1(eslint@10.11.0(supports-color@7.2.0)) - '@eslint/css-tree': 4.1.0 - browserslist: 4.29.0 + '@eslint-community/eslint-utils': 4.10.1(eslint@10.12.0(supports-color@7.2.0)) + '@eslint/css-tree': 4.1.1 + browserslist: 4.29.3 change-case: 5.4.4 ci-info: 4.4.0 core-js-compat: 3.50.0 detect-indent: 7.0.2 entities: 8.1.0 - eslint: 10.11.0(supports-color@7.2.0) + eslint: 10.12.0(supports-color@7.2.0) find-up-simple: 1.0.1 globals: 17.12.0 identifier-regex: 1.1.0 @@ -3423,7 +3598,7 @@ snapshots: is-builtin-module: 5.0.0 pluralize: 8.0.0 quote-js-string: 0.1.0 - regjsparser: 0.13.2 + regjsparser: 0.13.3 reserved-identifiers: 1.2.0 semver: 7.8.5 strip-indent: 4.1.1 @@ -3440,9 +3615,9 @@ snapshots: eslint-visitor-keys@5.0.1: {} - eslint@10.11.0(supports-color@7.2.0): + eslint@10.12.0(supports-color@7.2.0): dependencies: - '@eslint-community/eslint-utils': 4.10.1(eslint@10.11.0(supports-color@7.2.0)) + '@eslint-community/eslint-utils': 4.10.1(eslint@10.12.0(supports-color@7.2.0)) '@eslint-community/regexpp': 4.12.2 '@eslint/config-array': 0.23.5(supports-color@7.2.0) '@eslint/config-helpers': 0.7.0 @@ -3597,6 +3772,8 @@ snapshots: globals@17.12.0: {} + globals@17.13.0: {} + globalthis@1.0.4: dependencies: define-properties: 1.2.1 @@ -3902,7 +4079,7 @@ snapshots: math-intrinsics@1.1.0: {} - mdn-data@2.34.0: {} + mdn-data@2.35.0: {} micromatch@4.0.8: dependencies: @@ -3933,6 +4110,8 @@ snapshots: node-releases@2.0.55: {} + node-releases@2.0.57: {} + object-inspect@1.13.4: {} object-keys@1.1.1: {} @@ -4055,7 +4234,7 @@ snapshots: gopd: 1.2.0 set-function-name: 2.0.2 - regjsparser@0.13.2: + regjsparser@0.13.3: dependencies: jsesc: 3.1.0 @@ -4290,13 +4469,13 @@ snapshots: possible-typed-array-names: 1.1.0 reflect.getprototypeof: 1.0.10 - typescript-eslint@8.70.0(eslint@10.11.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3): + typescript-eslint@8.71.0(eslint@10.12.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3): dependencies: - '@typescript-eslint/eslint-plugin': 8.70.0(@typescript-eslint/parser@8.70.0(eslint@10.11.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3))(eslint@10.11.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) - '@typescript-eslint/parser': 8.70.0(eslint@10.11.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) - '@typescript-eslint/typescript-estree': 8.70.0(supports-color@7.2.0)(typescript@6.0.3) - '@typescript-eslint/utils': 8.70.0(eslint@10.11.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) - eslint: 10.11.0(supports-color@7.2.0) + '@typescript-eslint/eslint-plugin': 8.71.0(@typescript-eslint/parser@8.71.0(eslint@10.12.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3))(eslint@10.12.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) + '@typescript-eslint/parser': 8.71.0(eslint@10.12.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) + '@typescript-eslint/typescript-estree': 8.71.0(supports-color@7.2.0)(typescript@6.0.3) + '@typescript-eslint/utils': 8.71.0(eslint@10.12.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) + eslint: 10.12.0(supports-color@7.2.0) typescript: 6.0.3 transitivePeerDependencies: - supports-color @@ -4346,6 +4525,12 @@ snapshots: escalade: 3.2.0 picocolors: 1.1.1 + update-browserslist-db@1.3.3(browserslist@4.29.3): + dependencies: + browserslist: 4.29.3 + escalade: 3.2.0 + picocolors: 1.1.1 + uri-js@4.4.1: dependencies: punycode: 2.3.1 @@ -4361,10 +4546,10 @@ snapshots: fsevents: 2.3.3 yaml: 2.9.1 - vitest@5.0.2(@vitest/coverage-v8@5.0.2)(vite@8.3.0(yaml@2.9.1)): + vitest@5.0.3(@vitest/coverage-v8@5.0.3)(vite@8.3.0(yaml@2.9.1)): dependencies: '@types/chai': 5.2.3 - '@vitest/mocker': 5.0.2(vite@8.3.0(yaml@2.9.1)) + '@vitest/mocker': 5.0.3(vite@8.3.0(yaml@2.9.1)) chai: 6.2.2 es-module-lexer: 2.3.2 expect-type: 1.4.0 @@ -4376,9 +4561,9 @@ snapshots: tinyexec: 1.3.0 tinyglobby: 0.2.17 vite: 8.3.0(yaml@2.9.1) - why-is-node-running: 3.2.2 + why-is-node-running: 3.2.1 optionalDependencies: - '@vitest/coverage-v8': 5.0.2(vitest@5.0.2) + '@vitest/coverage-v8': 5.0.3(vitest@5.0.3) transitivePeerDependencies: - msw @@ -4427,7 +4612,7 @@ snapshots: dependencies: isexe: 2.0.0 - why-is-node-running@3.2.2: {} + why-is-node-running@3.2.1: {} word-wrap@1.2.5: {} diff --git a/ts_config/pnpm-workspace.yaml b/ts_config/pnpm-workspace.yaml index 1846b62..ec30e94 100644 --- a/ts_config/pnpm-workspace.yaml +++ b/ts_config/pnpm-workspace.yaml @@ -2,3 +2,16 @@ allowBuilds: unrs-resolver: false minimumReleaseAgeExclude: - eslint-plugin-unicorn@75.0.0 + - '@eslint-react/ast@5.23.3' + - '@eslint-react/core@5.23.3' + - '@eslint-react/eslint-plugin@5.23.3' + - '@eslint-react/eslint@5.23.3' + - '@eslint-react/jsx@5.23.3' + - '@eslint-react/shared@5.23.3' + - '@eslint-react/var@5.23.3' + - eslint-plugin-react-dom@5.23.3 + - eslint-plugin-react-jsx@5.23.3 + - eslint-plugin-react-naming-convention@5.23.3 + - eslint-plugin-react-rsc@5.23.3 + - eslint-plugin-react-web-api@5.23.3 + - eslint-plugin-react-x@5.23.3 diff --git a/ts_config/scripts/lib/alias-imports.mjs b/ts_config/scripts/lib/alias-imports.mjs index 5b9dc34..7ce0b5b 100644 --- a/ts_config/scripts/lib/alias-imports.mjs +++ b/ts_config/scripts/lib/alias-imports.mjs @@ -69,20 +69,27 @@ export function rewriteSource(file, text, sourceDirectory) { } /** - * Rewrite every source file under `/` in place. + * Rewrite every source file under `/` in place -- and under + * each extra directory, whose imports are still aliased against `source` + * (a `tests/` tree importing `../src/x` becomes `@/x`). * * @param {string} root Repo root. * @param {string} [source] Aliased directory, relative to root. + * @param {string[]} [extra] More directories to walk, relative to root. * @returns {{ files: number, imports: number }} What changed. */ -export function aliasImports(root, source = "src") { +export function aliasImports(root, source = "src", extra = []) { const sourceDirectory = path.resolve(root, source); - if (!fs.existsSync(sourceDirectory)) { - throw new Error(`alias-imports: no such directory ${sourceDirectory}`); + const walked = [sourceDirectory, ...extra.map((directory) => path.resolve(root, directory))]; + for (const directory of walked) { + if (!fs.existsSync(directory)) { + throw new Error(`alias-imports: no such directory ${directory}`); + } } let files = 0; let imports = 0; - for (const file of listSourceFiles(sourceDirectory)) { + const candidates = walked.flatMap((directory) => listSourceFiles(directory)); + for (const file of candidates) { const before = fs.readFileSync(file, "utf8"); const { count, text } = rewriteSource(file, before, sourceDirectory); if (count === 0) { @@ -96,19 +103,20 @@ export function aliasImports(root, source = "src") { } /** - * CLI body: `alias-imports [src-dir]`. Returns the exit code - * and writes the one-line report, so the shim stays at two lines. + * CLI body: `alias-imports [src-dir] [extra-dir...]`. Returns + * the exit code and writes the one-line report, so the shim stays at two + * lines. * * @param {string[]} argv Arguments after the script name. * @param {{ log: (line: string) => void }} [io] */ export function main(argv, io = console) { - const [root, source] = argv; + const [root, source, ...extra] = argv; if (root === undefined) { - io.log("usage: alias-imports [src-dir=src]"); + io.log("usage: alias-imports [src-dir=src] [extra-dir...]"); return 2; } - const { files, imports } = aliasImports(root, source); + const { files, imports } = aliasImports(root, source, extra); io.log(`alias-imports: rewrote ${imports} import(s) in ${files} file(s) under ${path.resolve(root, source ?? "src")}`); return 0; } diff --git a/ts_config/test/alias-imports.test.js b/ts_config/test/alias-imports.test.js index f4ae03e..d3a1859 100644 --- a/ts_config/test/alias-imports.test.js +++ b/ts_config/test/alias-imports.test.js @@ -28,6 +28,7 @@ const FIXTURE = { "src/services/api.ts": 'import { format } from "../utils/format";\nexport const api = format;\n', "src/services/notes.md": "not source: [x](../utils/format)\n", "src/utils/format.ts": "export const format = 1;\n", + "tests/api.test.ts": 'import { api } from "../src/services/api";\nimport { fixture } from "./fixture";\nexport const t = api(fixture);\n', "tooling/outside.ts": "export const outside = 1;\n", }; @@ -111,8 +112,15 @@ describe("aliasImports", () => { expect(aliasImports(rootOf(), "lib")).toEqual({ files: 2, imports: 6 }); }); - it("fails loudly on a missing source dir", () => { + it("walks extra directories, aliasing them against src", () => { + expect(aliasImports(rootOf(), "src", ["tests"])).toEqual({ files: 3, imports: 7 }); + expect(read("tests/api.test.ts")).toContain('from "@/services/api"'); + expect(read("tests/api.test.ts")).toContain('from "./fixture"'); + }); + + it("fails loudly on a missing source or extra dir", () => { expect(() => aliasImports(rootOf(), "nope")).toThrow(/no such directory/); + expect(() => aliasImports(rootOf(), "src", ["nope"])).toThrow(/no such directory/); }); }); @@ -130,8 +138,8 @@ describe("main", () => { }); it("runs as a CLI", () => { - const stdout = execFileSync(process.execPath, [CLI, rootOf()], { encoding: "utf8" }); - expect(stdout).toContain("rewrote 6 import(s) in 2 file(s)"); + const stdout = execFileSync(process.execPath, [CLI, rootOf(), "src", "tests"], { encoding: "utf8" }); + expect(stdout).toContain("rewrote 7 import(s) in 3 file(s)"); expect(read("src/services/api.ts")).toContain("@/utils/format"); }); }); diff --git a/ts_core/package.json b/ts_core/package.json index 7fc7cd5..effd71e 100644 --- a/ts_core/package.json +++ b/ts_core/package.json @@ -9,7 +9,7 @@ "node": ">=24", "pnpm": ">=10" }, - "packageManager": "pnpm@12.6.0+sha512.3ef68f951cb111ac204b4a5a16f0b2ddf0da56a96e0413e81d855d9f0b55ef926714709028e1cd00c405c2c5fb7b9e8ec4dc46777c805d0373c2f2ff00fd20ec", + "packageManager": "pnpm@12.9.0+sha512.8f64efa0b792986ca98ae568791bcd7722807e274c4f0c1d9853c62116495c584119db98762a2a5343b1684d361b9ae26fdd16d551666d7635346d3fae830ddd", "files": [ "dist" ], @@ -30,11 +30,11 @@ "devDependencies": { "@eslint/js": "10.0.1", "@kuhyx/ts-config": "link:../ts_config", - "@types/node": "26.6.3", - "@vitest/coverage-v8": "5.0.2", - "eslint": "10.11.0", + "@types/node": "26.6.4", + "@vitest/coverage-v8": "5.0.3", + "eslint": "10.12.0", "typescript": "6.0.3", - "typescript-eslint": "8.70.0", - "vitest": "5.0.2" + "typescript-eslint": "8.71.0", + "vitest": "5.0.3" } } diff --git a/ts_core/pnpm-lock.yaml b/ts_core/pnpm-lock.yaml index afecf86..76b74de 100644 --- a/ts_core/pnpm-lock.yaml +++ b/ts_core/pnpm-lock.yaml @@ -7,153 +7,153 @@ importers: configDependencies: {} packageManagerDependencies: pnpm: - specifier: 12.6.0 - version: 12.6.0 + specifier: 12.9.0 + version: 12.9.0 packages: - '@pnpm/exe.android-arm64@12.6.0': - resolution: {integrity: sha512-kviIHft9h02q+7N2In7tSL9T/HRUSGwYmU74YPtsWU5ee7vgqPyC0JT6RRX6miW+NxX1CvHgQxg69qq1qTLJYQ==} + '@pnpm/exe.android-arm64@12.9.0': + resolution: {integrity: sha512-/53fHFwP1mBYQjli6h3LBZsocAMjrEY6hCDiomIwdLIFbtxHrWucPg/s8ozOG3K6Qt8yEA78v7fCcNrgZKTlEQ==} cpu: [arm64] os: [android] - '@pnpm/exe.android-x64@12.6.0': - resolution: {integrity: sha512-CT8aJKLq2mtZFE71pr4E5Z2xHL8uGnRGr+NclDqsXlVF4SVcQ2QAs14mWi0C8gjT4hbmi+Q0lJRRIkMAWfjjng==} + '@pnpm/exe.android-x64@12.9.0': + resolution: {integrity: sha512-QI+Md3q3Df0WbDGpqciRw9ge0r7CGbxSlP3HcyIf34/YdZsbmUTl416oWOe3RrkZpVVVCA20f8B9esYrITpArA==} cpu: [x64] os: [android] - '@pnpm/exe.darwin-arm64@12.6.0': - resolution: {integrity: sha512-rafpVkjzugKBMxSvGQd5wK1x5eOTkD/+lcJIFHeQhvd+BJ8kDisOOvwhQDrpGd4vd2Fx+hhW0P2Ptt638OlhFg==} + '@pnpm/exe.darwin-arm64@12.9.0': + resolution: {integrity: sha512-mWr7fGPYJ12GuvU/q0SyJfj7X/9Eqa5dA6cc+yUUAKRdYEcqW4lQcsDd1ArfxJNSDYooz9X+UhFeVxKO9e/xDA==} cpu: [arm64] os: [darwin] - '@pnpm/exe.darwin-x64@12.6.0': - resolution: {integrity: sha512-72Jpuv1m24gI8zUNcaylYpBWEhBzz1VPhnDIU2GYM2eRP+KsOqVLwV78i+JrihI39zY2NNXmaN4eAKX1inpaDQ==} + '@pnpm/exe.darwin-x64@12.9.0': + resolution: {integrity: sha512-fr4ns4FyMLptdYbWqW3yc/yvbfQP7D1RBCFUJpSPr7tOzd3JxZvl+Tkvg65kEAGU5wXI6vl3eCOofumEj/n3Gw==} cpu: [x64] os: [darwin] - '@pnpm/exe.freebsd-x64@12.6.0': - resolution: {integrity: sha512-LON4QgNy1w/XF4uySZIyC/5hEB0oGdelXsY9tSKqstER+2j2X2oHz67skD11RRd/HWFRtMr64shQZ4I9ZoZ0zg==} + '@pnpm/exe.freebsd-x64@12.9.0': + resolution: {integrity: sha512-4+hZPeJzSSw1oOrfUg+v4QylHv6qKhdgcCnKt9ooryE79p1Z9oyozVJ5em+g6ajbvv6jlH+epnht8/s9Cp5M3Q==} cpu: [x64] os: [freebsd] - '@pnpm/exe.linux-arm64-musl@12.6.0': - resolution: {integrity: sha512-BdDpX+DeaMUc5x6WNcE6FkmFy36NwrBbBvwsZU737Zt3u1fTY+iKFPchjEQNCM4rT6q6xvF2oN3ZwExGsJCkxQ==} + '@pnpm/exe.linux-arm64-musl@12.9.0': + resolution: {integrity: sha512-+mHQSbA6M6W71PyV0IxYu2T43lZA8rAhlw4Gy7EMlyiv8H2GWJAXtUXl2huw7tnsar2AhCRlPn/ZvUv+/mIBkA==} cpu: [arm64] os: [linux] libc: [musl] - '@pnpm/exe.linux-arm64@12.6.0': - resolution: {integrity: sha512-8h2sNoIhHDpHDYqZZCh9PXr6krqW10btb0GHVGuw710muDHlFAHMtUG2ogmaHge6zJG1xgY4Y7mwX5g/LWvrYg==} + '@pnpm/exe.linux-arm64@12.9.0': + resolution: {integrity: sha512-U1mBoG27+J/HuWYtVZOQrvbiKnTlDsDVzyroKQqHsJaTHTXi0H5Y+fU1GjUyMcNG5r/EXtbhp9iAwQt64uXTxQ==} cpu: [arm64] os: [linux] libc: [glibc] - '@pnpm/exe.linux-ppc64@12.6.0': - resolution: {integrity: sha512-ld6xgcEhFCFrsVsJhbmdmR714PnsEspO/Ij8w0/D7OSiz2hYGu3fZd5tKRxOI5m6H6Gl0/RRFAPncTHbsgXVjQ==} + '@pnpm/exe.linux-ppc64@12.9.0': + resolution: {integrity: sha512-P5bgei3i6wjlKq6s4PNg/0cvik354upY2wW3YGSfweYTABnAZP2zGzgMc0eO5SHTz934Cbya8Z0YFRdw6MfmCg==} cpu: [ppc64] os: [linux] libc: [glibc] - '@pnpm/exe.linux-riscv64@12.6.0': - resolution: {integrity: sha512-HIbImydoC3J8NFt+8MXfhTCf5rO6NjCMyinSAHac0VmV8uCHw+kbQkyAzxV84rzz9ZOzmBOnj0b1N461UIYGsw==} + '@pnpm/exe.linux-riscv64@12.9.0': + resolution: {integrity: sha512-oNx2uOo2gVrRckEvz7V+FKeq9HOF0rKNHcCyxRRHSXEXpmP6ZMMgJ+iU/m/zfSGp6bAcC5mvAE270FhAeIeKhA==} cpu: [riscv64] os: [linux] libc: [glibc] - '@pnpm/exe.linux-s390x@12.6.0': - resolution: {integrity: sha512-DjSqT7+BZ/lWcDHwNMuzsRVfyxUimh+D54Xw4tSWbdVVQhtWtNMUyI93LVnwCmsoEpar2gC3SFeAgywx37svKQ==} + '@pnpm/exe.linux-s390x@12.9.0': + resolution: {integrity: sha512-OChHS7hOlqdBMtZqjIB1QoZGNfzLPSsvTa2kYanf3qso5Q+KgnROQ+uV4a0wRGC6sdshSA63+Ui8MCylRy6iwA==} cpu: [s390x] os: [linux] libc: [glibc] - '@pnpm/exe.linux-x64-musl@12.6.0': - resolution: {integrity: sha512-31lKeGPmRE6xfV6I3VjaEGVFRei14pl/zgoFDK4w+UkbJqOuNl2Ht3O1GuqNIg3Gl14qTEb4kZRAYFHXifDx9A==} + '@pnpm/exe.linux-x64-musl@12.9.0': + resolution: {integrity: sha512-ubZPsDMXS+LEnPa7wZD18E+KTpOAZPt+ms/khfIROt+6Cp0A2kayQA8aIsvV7+jGZD0xoknvaGN/P3v9OisZUg==} cpu: [x64] os: [linux] libc: [musl] - '@pnpm/exe.linux-x64@12.6.0': - resolution: {integrity: sha512-qFWBneHJAJ73W4whtbaFOL1M/7DBC6ILHXuxc7ZPtEhfPuT1zeZiGrmKHoMAfJA+mcm6xhOFljqVTUS+00Jabw==} + '@pnpm/exe.linux-x64@12.9.0': + resolution: {integrity: sha512-ElANYUwQuMfWqMJR4m5L5h9zKjvGKcA8hi2put4J1DMx2smVaNGkGnxkgeIYgMjmF7UFxlZDcL4jItkScgsW4A==} cpu: [x64] os: [linux] libc: [glibc] - '@pnpm/exe.win32-arm64@12.6.0': - resolution: {integrity: sha512-OhfefXEEykZlslSUhR8PPRe6MVPV3Oink/dVfIecE5velpc7j80cZYsHlaDrtRDcm3TPwQHkaRW1SJ1efX9oFg==} + '@pnpm/exe.win32-arm64@12.9.0': + resolution: {integrity: sha512-cqnBKZg5e9fFghmYtgJLyZOAsS1EyLeDVJ0qNsjR7hcc+dq1mWRiDo0erqdj2PGoYaVogyF9WejZSJoKvlfXDw==} cpu: [arm64] os: [win32] - '@pnpm/exe.win32-x64@12.6.0': - resolution: {integrity: sha512-L2tuyrD2+Imgxs3VK/ST/2L3xD6vDP0ktJQxM0TaGw7TbAQEMq+RMc8iK6Ew7Id579uzjJe8jSfJID2ZGsOHCA==} + '@pnpm/exe.win32-x64@12.9.0': + resolution: {integrity: sha512-khqst6QGgHdKs2lEmz0N75qN/EoOcEywNQlRPfBrBrq1SytHra6ro/xViueJkVvDOz9gEO8oYcXTdZeAjErjMg==} cpu: [x64] os: [win32] - pnpm@12.6.0: - resolution: {integrity: sha512-PvaPlRyxEawgS0paFvCy3fDaVqluBBPoHYVdnwtV75JnFHCQKOHNAMQFwsX7e56OxNxGd3yAXQNzwvL/AP0g7A==} + pnpm@12.9.0: + resolution: {integrity: sha512-j2TvoLeSmGypiuVoeRvNdyKAfidMTwwdmFPGIRZJXFhBGduYdioqU0OxaE02G5rib90W1VFmbXY1NG0/roMN3Q==} engines: {node: '>=18.*'} hasBin: true snapshots: - '@pnpm/exe.android-arm64@12.6.0': + '@pnpm/exe.android-arm64@12.9.0': optional: true - '@pnpm/exe.android-x64@12.6.0': + '@pnpm/exe.android-x64@12.9.0': optional: true - '@pnpm/exe.darwin-arm64@12.6.0': + '@pnpm/exe.darwin-arm64@12.9.0': optional: true - '@pnpm/exe.darwin-x64@12.6.0': + '@pnpm/exe.darwin-x64@12.9.0': optional: true - '@pnpm/exe.freebsd-x64@12.6.0': + '@pnpm/exe.freebsd-x64@12.9.0': optional: true - '@pnpm/exe.linux-arm64-musl@12.6.0': + '@pnpm/exe.linux-arm64-musl@12.9.0': optional: true - '@pnpm/exe.linux-arm64@12.6.0': + '@pnpm/exe.linux-arm64@12.9.0': optional: true - '@pnpm/exe.linux-ppc64@12.6.0': + '@pnpm/exe.linux-ppc64@12.9.0': optional: true - '@pnpm/exe.linux-riscv64@12.6.0': + '@pnpm/exe.linux-riscv64@12.9.0': optional: true - '@pnpm/exe.linux-s390x@12.6.0': + '@pnpm/exe.linux-s390x@12.9.0': optional: true - '@pnpm/exe.linux-x64-musl@12.6.0': + '@pnpm/exe.linux-x64-musl@12.9.0': optional: true - '@pnpm/exe.linux-x64@12.6.0': + '@pnpm/exe.linux-x64@12.9.0': optional: true - '@pnpm/exe.win32-arm64@12.6.0': + '@pnpm/exe.win32-arm64@12.9.0': optional: true - '@pnpm/exe.win32-x64@12.6.0': + '@pnpm/exe.win32-x64@12.9.0': optional: true - pnpm@12.6.0: + pnpm@12.9.0: optionalDependencies: - '@pnpm/exe.android-arm64': 12.6.0 - '@pnpm/exe.android-x64': 12.6.0 - '@pnpm/exe.darwin-arm64': 12.6.0 - '@pnpm/exe.darwin-x64': 12.6.0 - '@pnpm/exe.freebsd-x64': 12.6.0 - '@pnpm/exe.linux-arm64': 12.6.0 - '@pnpm/exe.linux-arm64-musl': 12.6.0 - '@pnpm/exe.linux-ppc64': 12.6.0 - '@pnpm/exe.linux-riscv64': 12.6.0 - '@pnpm/exe.linux-s390x': 12.6.0 - '@pnpm/exe.linux-x64': 12.6.0 - '@pnpm/exe.linux-x64-musl': 12.6.0 - '@pnpm/exe.win32-arm64': 12.6.0 - '@pnpm/exe.win32-x64': 12.6.0 + '@pnpm/exe.android-arm64': 12.9.0 + '@pnpm/exe.android-x64': 12.9.0 + '@pnpm/exe.darwin-arm64': 12.9.0 + '@pnpm/exe.darwin-x64': 12.9.0 + '@pnpm/exe.freebsd-x64': 12.9.0 + '@pnpm/exe.linux-arm64': 12.9.0 + '@pnpm/exe.linux-arm64-musl': 12.9.0 + '@pnpm/exe.linux-ppc64': 12.9.0 + '@pnpm/exe.linux-riscv64': 12.9.0 + '@pnpm/exe.linux-s390x': 12.9.0 + '@pnpm/exe.linux-x64': 12.9.0 + '@pnpm/exe.linux-x64-musl': 12.9.0 + '@pnpm/exe.win32-arm64': 12.9.0 + '@pnpm/exe.win32-x64': 12.9.0 --- lockfileVersion: '9.0' @@ -168,28 +168,28 @@ importers: devDependencies: '@eslint/js': specifier: 10.0.1 - version: 10.0.1(eslint@10.11.0(supports-color@7.2.0)) + version: 10.0.1(eslint@10.12.0(supports-color@7.2.0)) '@kuhyx/ts-config': specifier: link:../ts_config version: link:../ts_config '@types/node': - specifier: 26.6.3 - version: 26.6.3 + specifier: 26.6.4 + version: 26.6.4 '@vitest/coverage-v8': - specifier: 5.0.2 - version: 5.0.2(vitest@5.0.2) + specifier: 5.0.3 + version: 5.0.3(vitest@5.0.3) eslint: - specifier: 10.11.0 - version: 10.11.0(supports-color@7.2.0) + specifier: 10.12.0 + version: 10.12.0(supports-color@7.2.0) typescript: specifier: 6.0.3 version: 6.0.3 typescript-eslint: - specifier: 8.70.0 - version: 8.70.0(eslint@10.11.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) + specifier: 8.71.0 + version: 8.71.0(eslint@10.12.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) vitest: - specifier: 5.0.2 - version: 5.0.2(@types/node@26.6.3)(@vitest/coverage-v8@5.0.2)(vite@8.2.1(@types/node@26.6.3)) + specifier: 5.0.3 + version: 5.0.3(@types/node@26.6.4)(@vitest/coverage-v8@5.0.3)(vite@8.2.1(@types/node@26.6.4)) packages: @@ -409,73 +409,73 @@ packages: '@types/json-schema@7.0.15': resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} - '@types/node@26.6.3': - resolution: {integrity: sha512-dsqMQQoeTLqu9wynDD00q573mNzso3IdQOAfHRJqLCcmCFPoGo9A1bDpUcv/9tnKpErQWv9uKeGfl37EIS02Yg==} + '@types/node@26.6.4': + resolution: {integrity: sha512-ldVPDCzj7fsaGZrLB0NuHuTvJcsNasysBAqMolr/cgxrLd1xbqxIr3XJiPnHHJUCxj5sNF1vnRj9aWnrVh5Jcg==} - '@typescript-eslint/eslint-plugin@8.70.0': - resolution: {integrity: sha512-/v8HZt6RlyIZxB3ntehELOcUcfxKPVGWXnQdJuHRmzrqgF8nQypcC/oxGW+Ot4VGKDq81XugPKxx0n5PBtf9PA==} + '@typescript-eslint/eslint-plugin@8.71.0': + resolution: {integrity: sha512-pqcS9c1HxZTHt7End4nXqd0s5lJrrFzrgCkKFJrsbUnaL6M3+6oBFZaslg6Gjsl3argl2DDRFROnXARaZ2e4Nw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - '@typescript-eslint/parser': ^8.70.0 + '@typescript-eslint/parser': ^8.71.0 eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/parser@8.70.0': - resolution: {integrity: sha512-zYvrmj9Yxd63UGaXw+kdt6A0F0s0qveJyuatIM77bYC2DE4pgmg7a50u8LR7PRtXd0x+h+Tl3eXabGm06SWd3Q==} + '@typescript-eslint/parser@8.71.0': + resolution: {integrity: sha512-CG4nPk1f2zc8yw4pALqHsFYH2hdo+h1T9daSp21+Hnxi9LOE3GT9hAfTKJCBXVNM2GmYs1eMEP615wPoeOgk3A==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/project-service@8.70.0': - resolution: {integrity: sha512-hFHbTNqhU9G+2eKFXCBVb1tjFT/LceiJ4+HfLO4pTpDI0KHi6iajpcFFkaSQ9gXmCh7n82A0PthaayEdN6mspQ==} + '@typescript-eslint/project-service@8.71.0': + resolution: {integrity: sha512-aABjw5rjBacYONVPaPiWOCjJu0vEF4a25iQuodlmQYL1trtLZ0X/y+2Vzl3BKI1odM4LnwLE1oUDXYp1wzx1TQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/scope-manager@8.70.0': - resolution: {integrity: sha512-8nP3Kwh5hlgZ4FicGvmznAmJe8UL4sdU8tLukrPaMuQmDuk4Y8xYfzu/aYZW4xT2JCgc7H/TpDI5cGlxcWJSqQ==} + '@typescript-eslint/scope-manager@8.71.0': + resolution: {integrity: sha512-gWF0BhUcnjZxSpLE8ngS/59n2SB0J3YqRxvX1+2aoRJk9hNtHSLOV+TcarFiOr5ipXm3yc1QrI4c9YZc8zyCxw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/tsconfig-utils@8.70.0': - resolution: {integrity: sha512-adnkeeNq9Sq1sUf4+FRVc0KdgYghzsgFpZSQVZVvY0LCuUuN0FnQgyGzCJeC4fW1cdXseBAjU2EOqUIjbNcZUw==} + '@typescript-eslint/tsconfig-utils@8.71.0': + resolution: {integrity: sha512-Z1UlWHADEK2Mlb9NpWfDeSjqoZ5EyrOv4R3eQpbkzqn/EwaIdOpXXupEA1+0ZIOSJSZZDBHG0BrQyN8zUG6Pwg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/type-utils@8.70.0': - resolution: {integrity: sha512-NUMKIhYVaVIVLnRL9CRt+VVcuLgSHUCpXn4/+K8wql+vdInUzvx8BjUO1oJ7cG9shjFJKtF8F8Hh2kCh3/KBVw==} + '@typescript-eslint/type-utils@8.71.0': + resolution: {integrity: sha512-i8uO1qbdxeKgRnS5sCRt6On3/nfo2d2DwQe3Yvjx543zLy7r8ySqRuPPiIIXAhS03U0v5NfAFx+rUgxFzKKwNw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/types@8.70.0': - resolution: {integrity: sha512-asTOIYhDg4zdzOScCyaytrsV3cR6B4ecPQlXw/dJIm7J/MZTtCtfVII9JD8Geh4jTCrK/Xe6cg5UevoleMcoJQ==} + '@typescript-eslint/types@8.71.0': + resolution: {integrity: sha512-cJ4OoxPGWvFnBTnSZyaU+qJzGTqPTGJY+gDchj6cRyLRdmIdt4rcsE4twj+zPfrNiWuVi38wijHzShL++Z9atQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/typescript-estree@8.70.0': - resolution: {integrity: sha512-d9NmHMPEKQ7QCLLm1jI3zmoQBwT5KwFYjXBJ9ymZfKCUU+5rmTRykKAFvH5Qn/ZCds3CEAFS9OC9M/jkl0X2bA==} + '@typescript-eslint/typescript-estree@8.71.0': + resolution: {integrity: sha512-PEEF4G5sLLWAS5BpPrUvms4ySZkiBQQZM4z+3ReI46axK5Vqr/vXBQatJQIZZOYdGyPUAKTtsrWzpqKuU+3DEw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/utils@8.70.0': - resolution: {integrity: sha512-oZmtKJz/4fufZ2p3+Cn3ijEojcdfR+1zYDH2xKYrEly0dR/Q/1xUPRCOlKGxod78nWlU2UnDe09GZ3TaknBFGA==} + '@typescript-eslint/utils@8.71.0': + resolution: {integrity: sha512-pKR/tEMVrXZG23UFKUn5BQf3zfmfk7KQceI2cGzywZ5nxM5Eu3hEJU1utjWzydtzBbcJAQhHN8iPCxobHpPcZQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/visitor-keys@8.70.0': - resolution: {integrity: sha512-BoC8PiO4Hkdo0TVJh9Ntxr5MxPDI7/oFsrygN5ADelFSeXG/qgNuucIGA+L5Z6JpPTE/uRfcTWtscjbUaufepQ==} + '@typescript-eslint/visitor-keys@8.71.0': + resolution: {integrity: sha512-8eQ9R218XORK+KLosnf4bu/QsUXvUyVwTbArg7/0NMB1Pu87OJKvj4nhFblkYE8gQV73mW1dx1ptlPCkwRGa7A==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@vitest/coverage-v8@5.0.2': - resolution: {integrity: sha512-3ffHBEi8DOOBLwIGBhOBZbRfYFYWjMUuxZicONdhuFEFEG5AVsMOSrVeuROskqnqpbOmEJwxM2eTVZ9N3a1t+A==} + '@vitest/coverage-v8@5.0.3': + resolution: {integrity: sha512-+klsyz7BvT1vCU28Zkfzms1Ia78XD0V41U3FUtRaa3S+vOr/EXvK1O6BvVD7l1RzEjm6SONR2aybOvDDf9u0mQ==} peerDependencies: - '@vitest/browser': 5.0.2 - vitest: 5.0.2 + '@vitest/browser': 5.0.3 + vitest: 5.0.3 peerDependenciesMeta: '@vitest/browser': optional: true @@ -488,8 +488,8 @@ packages: resolution: {integrity: sha512-1EOLRfsTMnyAr3+kEAsP4o9dhaDlGPpD7H5iLBBeq//YpNB1VIahkPhB+eRp9N2Dkfw8oySROjE3yf9XDeaIkQ==} engines: {node: '>=22'} - '@vitest/mocker@5.0.2': - resolution: {integrity: sha512-Z5FS00Q1SJHkB35xATsmWGdQ5WA1/0MV3CDjqyv7GavHv1OfOj145MNfHOlHk7QLes21dKFDHr8EO2zvL+9WGA==} + '@vitest/mocker@5.0.3': + resolution: {integrity: sha512-T8sWAIbkSyAjkwTcaEc3Iu0o9A27X1/kdXrizhZkGuSKScRQtRzclfAMpOTcGdXCsqxeWlpGy3XjqaW8CpLORg==} peerDependencies: msw: ^2.4.9 vite: ^6.0.0 || ^7.0.0 || ^8.0.0 @@ -499,8 +499,8 @@ packages: vite: optional: true - '@vitest/spy@5.0.2': - resolution: {integrity: sha512-Ijc7T1nT9efNb5LxvjaBrEqw3f/QwUv5EE0nKqZxgqsaV/FxAAZ8baGylA8X/Z2oS4Lp+K74Jr6dTJsDKxJDeg==} + '@vitest/spy@5.0.3': + resolution: {integrity: sha512-XhFysQTB8AZ+P4gMi+Lpo99vg2AZi0qKpaB9yXQl37+CaMEAPO3iH/wGVnSyL5MPERiLezpqTVtrR6UZH5GCXg==} acorn-jsx@5.3.2: resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} @@ -576,8 +576,8 @@ packages: resolution: {integrity: sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==} engines: {node: ^20.19.0 || ^22.13.0 || >=24} - eslint@10.11.0: - resolution: {integrity: sha512-P7a6UEEqb9G95MYAtqkmsTbVXIYyzIfl6NGOIJk162PaahFxFyeGcrlXYFSiagECg4sEm8IseJdZBKR3rx6MsQ==} + eslint@10.12.0: + resolution: {integrity: sha512-npHjrHb2o4ehH3mE+YuxABR+0gyb0aVWCIolgJDArwCiHEIsonBm3ZKjel5b0U5+a+MbEvIyRCGCzD3eKKK2NA==} engines: {node: ^20.19.0 || ^22.13.0 || >=24} hasBin: true peerDependencies: @@ -832,10 +832,6 @@ packages: picocolors@1.1.1: resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} - picomatch@4.0.5: - resolution: {integrity: sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==} - engines: {node: '>=12'} - picomatch@4.0.7: resolution: {integrity: sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==} engines: {node: '>=12'} @@ -911,8 +907,8 @@ packages: resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} engines: {node: '>= 0.8.0'} - typescript-eslint@8.70.0: - resolution: {integrity: sha512-P/W5cz70/cQAuKfY3xwQMWWTV7BvJ0mAQmi+9mBcsVPaBUpd6Ohpa+fECv9rBFrQcig86jAiNBFNWUqnTjr4pw==} + typescript-eslint@8.71.0: + resolution: {integrity: sha512-fBdHYiqQ14RW6mOMXD14Svn82ZsCYAoQSzGRzyEjR59S5A2Krh/l7fGTOQ7iCr8gGy/mHVXtEF7s5fgjEdV0Pw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 @@ -972,20 +968,20 @@ packages: yaml: optional: true - vitest@5.0.2: - resolution: {integrity: sha512-7MQrx9pDv5aHiUcovIb/70Ys3tgtkUVgCtledvKdCmEO+/1Dicq5ZqoSxOW034m03oqC+oHOKui2dM6qtMLoJg==} + vitest@5.0.3: + resolution: {integrity: sha512-xMw97S3rjdtj5dkVat7jCsqWBpvchs3RlpQctUqwJD0KkERk40vz2fJ77lDwW/Vzh/pk18eItYAzkodhSes3jQ==} engines: {node: ^22.12.0 || ^24.0.0 || >=26.0.0} hasBin: true peerDependencies: '@edge-runtime/vm': '*' '@opentelemetry/api': ^1.9.0 '@types/node': ^22.0.0 || >=24.0.0 - '@vitest/browser-playwright': 5.0.2 - '@vitest/browser-preview': 5.0.2 + '@vitest/browser-playwright': 5.0.3 + '@vitest/browser-preview': 5.0.3 '@vitest/browser-webdriverio': ^5.0.0-beta.5 || >=5.0.0 - '@vitest/coverage-istanbul': 5.0.2 - '@vitest/coverage-v8': 5.0.2 - '@vitest/ui': 5.0.2 + '@vitest/coverage-istanbul': 5.0.3 + '@vitest/coverage-v8': 5.0.3 + '@vitest/ui': 5.0.3 happy-dom: '*' jsdom: '*' vite: ^6.4.0 || ^7.0.0 || ^8.0.0 @@ -1018,8 +1014,8 @@ packages: engines: {node: '>= 8'} hasBin: true - why-is-node-running@3.2.2: - resolution: {integrity: sha512-NKUzAelcoCXhXL4dJzKIwXeR8iEVqsA0Lq6Vnd0UXvgaKbzVo4ZTHROF2Jidrv+SgxOQ03fMinnNhzZATxOD3A==} + why-is-node-running@3.2.1: + resolution: {integrity: sha512-Tb2FUhB4vUsGQlfSquQLYkApkuPAFQXGFzxWKHHumVz2dK+X1RUm/HnID4+TfIGYJ1kTcwOaCk/buYCEJr6YjQ==} engines: {node: '>=20.11'} hasBin: true @@ -1060,9 +1056,9 @@ snapshots: hashery: 1.5.1 keyv: 5.6.0 - '@eslint-community/eslint-utils@4.10.1(eslint@10.11.0(supports-color@7.2.0))': + '@eslint-community/eslint-utils@4.10.1(eslint@10.12.0(supports-color@7.2.0))': dependencies: - eslint: 10.11.0(supports-color@7.2.0) + eslint: 10.12.0(supports-color@7.2.0) eslint-visitor-keys: 3.4.3 '@eslint-community/regexpp@4.12.2': {} @@ -1083,9 +1079,9 @@ snapshots: dependencies: '@types/json-schema': 7.0.15 - '@eslint/js@10.0.1(eslint@10.11.0(supports-color@7.2.0))': + '@eslint/js@10.0.1(eslint@10.12.0(supports-color@7.2.0))': optionalDependencies: - eslint: 10.11.0(supports-color@7.2.0) + eslint: 10.12.0(supports-color@7.2.0) '@eslint/object-schema@3.0.5': {} @@ -1186,19 +1182,19 @@ snapshots: '@types/json-schema@7.0.15': {} - '@types/node@26.6.3': + '@types/node@26.6.4': dependencies: undici-types: 8.9.0 - '@typescript-eslint/eslint-plugin@8.70.0(@typescript-eslint/parser@8.70.0(eslint@10.11.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3))(eslint@10.11.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3)': + '@typescript-eslint/eslint-plugin@8.71.0(@typescript-eslint/parser@8.71.0(eslint@10.12.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3))(eslint@10.12.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3)': dependencies: '@eslint-community/regexpp': 4.12.2 - '@typescript-eslint/parser': 8.70.0(eslint@10.11.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) - '@typescript-eslint/scope-manager': 8.70.0 - '@typescript-eslint/type-utils': 8.70.0(eslint@10.11.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) - '@typescript-eslint/utils': 8.70.0(eslint@10.11.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) - '@typescript-eslint/visitor-keys': 8.70.0 - eslint: 10.11.0(supports-color@7.2.0) + '@typescript-eslint/parser': 8.71.0(eslint@10.12.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) + '@typescript-eslint/scope-manager': 8.71.0 + '@typescript-eslint/type-utils': 8.71.0(eslint@10.12.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) + '@typescript-eslint/utils': 8.71.0(eslint@10.12.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) + '@typescript-eslint/visitor-keys': 8.71.0 + eslint: 10.12.0(supports-color@7.2.0) ignore: 7.0.6 natural-compare: 1.4.0 ts-api-utils: 2.5.0(typescript@6.0.3) @@ -1206,56 +1202,56 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/parser@8.70.0(eslint@10.11.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3)': + '@typescript-eslint/parser@8.71.0(eslint@10.12.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3)': dependencies: - '@typescript-eslint/scope-manager': 8.70.0 - '@typescript-eslint/types': 8.70.0 - '@typescript-eslint/typescript-estree': 8.70.0(supports-color@7.2.0)(typescript@6.0.3) - '@typescript-eslint/visitor-keys': 8.70.0 + '@typescript-eslint/scope-manager': 8.71.0 + '@typescript-eslint/types': 8.71.0 + '@typescript-eslint/typescript-estree': 8.71.0(supports-color@7.2.0)(typescript@6.0.3) + '@typescript-eslint/visitor-keys': 8.71.0 debug: 4.4.3(supports-color@7.2.0) - eslint: 10.11.0(supports-color@7.2.0) + eslint: 10.12.0(supports-color@7.2.0) typescript: 6.0.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/project-service@8.70.0(supports-color@7.2.0)(typescript@6.0.3)': + '@typescript-eslint/project-service@8.71.0(supports-color@7.2.0)(typescript@6.0.3)': dependencies: - '@typescript-eslint/tsconfig-utils': 8.70.0(typescript@6.0.3) - '@typescript-eslint/types': 8.70.0 + '@typescript-eslint/tsconfig-utils': 8.71.0(typescript@6.0.3) + '@typescript-eslint/types': 8.71.0 debug: 4.4.3(supports-color@7.2.0) typescript: 6.0.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/scope-manager@8.70.0': + '@typescript-eslint/scope-manager@8.71.0': dependencies: - '@typescript-eslint/types': 8.70.0 - '@typescript-eslint/visitor-keys': 8.70.0 + '@typescript-eslint/types': 8.71.0 + '@typescript-eslint/visitor-keys': 8.71.0 - '@typescript-eslint/tsconfig-utils@8.70.0(typescript@6.0.3)': + '@typescript-eslint/tsconfig-utils@8.71.0(typescript@6.0.3)': dependencies: typescript: 6.0.3 - '@typescript-eslint/type-utils@8.70.0(eslint@10.11.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3)': + '@typescript-eslint/type-utils@8.71.0(eslint@10.12.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3)': dependencies: - '@typescript-eslint/types': 8.70.0 - '@typescript-eslint/typescript-estree': 8.70.0(supports-color@7.2.0)(typescript@6.0.3) - '@typescript-eslint/utils': 8.70.0(eslint@10.11.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) + '@typescript-eslint/types': 8.71.0 + '@typescript-eslint/typescript-estree': 8.71.0(supports-color@7.2.0)(typescript@6.0.3) + '@typescript-eslint/utils': 8.71.0(eslint@10.12.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) debug: 4.4.3(supports-color@7.2.0) - eslint: 10.11.0(supports-color@7.2.0) + eslint: 10.12.0(supports-color@7.2.0) ts-api-utils: 2.5.0(typescript@6.0.3) typescript: 6.0.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/types@8.70.0': {} + '@typescript-eslint/types@8.71.0': {} - '@typescript-eslint/typescript-estree@8.70.0(supports-color@7.2.0)(typescript@6.0.3)': + '@typescript-eslint/typescript-estree@8.71.0(supports-color@7.2.0)(typescript@6.0.3)': dependencies: - '@typescript-eslint/project-service': 8.70.0(supports-color@7.2.0)(typescript@6.0.3) - '@typescript-eslint/tsconfig-utils': 8.70.0(typescript@6.0.3) - '@typescript-eslint/types': 8.70.0 - '@typescript-eslint/visitor-keys': 8.70.0 + '@typescript-eslint/project-service': 8.71.0(supports-color@7.2.0)(typescript@6.0.3) + '@typescript-eslint/tsconfig-utils': 8.71.0(typescript@6.0.3) + '@typescript-eslint/types': 8.71.0 + '@typescript-eslint/visitor-keys': 8.71.0 debug: 4.4.3(supports-color@7.2.0) minimatch: 10.2.6 semver: 7.8.5 @@ -1265,23 +1261,23 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/utils@8.70.0(eslint@10.11.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3)': + '@typescript-eslint/utils@8.71.0(eslint@10.12.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3)': dependencies: - '@eslint-community/eslint-utils': 4.10.1(eslint@10.11.0(supports-color@7.2.0)) - '@typescript-eslint/scope-manager': 8.70.0 - '@typescript-eslint/types': 8.70.0 - '@typescript-eslint/typescript-estree': 8.70.0(supports-color@7.2.0)(typescript@6.0.3) - eslint: 10.11.0(supports-color@7.2.0) + '@eslint-community/eslint-utils': 4.10.1(eslint@10.12.0(supports-color@7.2.0)) + '@typescript-eslint/scope-manager': 8.71.0 + '@typescript-eslint/types': 8.71.0 + '@typescript-eslint/typescript-estree': 8.71.0(supports-color@7.2.0)(typescript@6.0.3) + eslint: 10.12.0(supports-color@7.2.0) typescript: 6.0.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/visitor-keys@8.70.0': + '@typescript-eslint/visitor-keys@8.71.0': dependencies: - '@typescript-eslint/types': 8.70.0 + '@typescript-eslint/types': 8.71.0 eslint-visitor-keys: 5.0.1 - '@vitest/coverage-v8@5.0.2(vitest@5.0.2)': + '@vitest/coverage-v8@5.0.3(vitest@5.0.3)': dependencies: '@bcoe/v8-coverage': 1.0.2 '@vitest/istanbul-lib-coverage': 1.0.1 @@ -1291,7 +1287,7 @@ snapshots: obug: 2.1.4 std-env: 4.2.0 tinyrainbow: 3.1.1 - vitest: 5.0.2(@types/node@26.6.3)(@vitest/coverage-v8@5.0.2)(vite@8.2.1(@types/node@26.6.3)) + vitest: 5.0.3(@types/node@26.6.4)(@vitest/coverage-v8@5.0.3)(vite@8.2.1(@types/node@26.6.4)) '@vitest/istanbul-lib-coverage@1.0.1': {} @@ -1299,16 +1295,16 @@ snapshots: dependencies: '@vitest/istanbul-lib-coverage': 1.0.1 - '@vitest/mocker@5.0.2(vite@8.2.1(@types/node@26.6.3))': + '@vitest/mocker@5.0.3(vite@8.2.1(@types/node@26.6.4))': dependencies: '@jridgewell/trace-mapping': 0.3.31 - '@vitest/spy': 5.0.2 + '@vitest/spy': 5.0.3 estree-walker: 3.0.3 magic-string: 1.3.1 optionalDependencies: - vite: 8.2.1(@types/node@26.6.3) + vite: 8.2.1(@types/node@26.6.4) - '@vitest/spy@5.0.2': {} + '@vitest/spy@5.0.3': {} acorn-jsx@5.3.2(acorn@8.18.0): dependencies: @@ -1378,9 +1374,9 @@ snapshots: eslint-visitor-keys@5.0.1: {} - eslint@10.11.0(supports-color@7.2.0): + eslint@10.12.0(supports-color@7.2.0): dependencies: - '@eslint-community/eslint-utils': 4.10.1(eslint@10.11.0(supports-color@7.2.0)) + '@eslint-community/eslint-utils': 4.10.1(eslint@10.12.0(supports-color@7.2.0)) '@eslint-community/regexpp': 4.12.2 '@eslint/config-array': 0.23.5(supports-color@7.2.0) '@eslint/config-helpers': 0.7.0 @@ -1443,9 +1439,9 @@ snapshots: fast-levenshtein@2.0.6: {} - fdir@6.5.0(picomatch@4.0.5): + fdir@6.5.0(picomatch@4.0.7): optionalDependencies: - picomatch: 4.0.5 + picomatch: 4.0.7 file-entry-cache@11.1.5: dependencies: @@ -1609,8 +1605,6 @@ snapshots: picocolors@1.1.1: {} - picomatch@4.0.5: {} - picomatch@4.0.7: {} postcss@8.5.26: @@ -1670,8 +1664,8 @@ snapshots: tinyglobby@0.2.17: dependencies: - fdir: 6.5.0(picomatch@4.0.5) - picomatch: 4.0.5 + fdir: 6.5.0(picomatch@4.0.7) + picomatch: 4.0.7 tinyrainbow@3.1.1: {} @@ -1683,13 +1677,13 @@ snapshots: dependencies: prelude-ls: 1.2.1 - typescript-eslint@8.70.0(eslint@10.11.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3): + typescript-eslint@8.71.0(eslint@10.12.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3): dependencies: - '@typescript-eslint/eslint-plugin': 8.70.0(@typescript-eslint/parser@8.70.0(eslint@10.11.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3))(eslint@10.11.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) - '@typescript-eslint/parser': 8.70.0(eslint@10.11.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) - '@typescript-eslint/typescript-estree': 8.70.0(supports-color@7.2.0)(typescript@6.0.3) - '@typescript-eslint/utils': 8.70.0(eslint@10.11.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) - eslint: 10.11.0(supports-color@7.2.0) + '@typescript-eslint/eslint-plugin': 8.71.0(@typescript-eslint/parser@8.71.0(eslint@10.12.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3))(eslint@10.12.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) + '@typescript-eslint/parser': 8.71.0(eslint@10.12.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) + '@typescript-eslint/typescript-estree': 8.71.0(supports-color@7.2.0)(typescript@6.0.3) + '@typescript-eslint/utils': 8.71.0(eslint@10.12.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) + eslint: 10.12.0(supports-color@7.2.0) typescript: 6.0.3 transitivePeerDependencies: - supports-color @@ -1702,7 +1696,7 @@ snapshots: dependencies: punycode: 2.3.1 - vite@8.2.1(@types/node@26.6.3): + vite@8.2.1(@types/node@26.6.4): dependencies: lightningcss: 1.33.0 picomatch: 4.0.7 @@ -1710,13 +1704,13 @@ snapshots: rolldown: 1.2.4 tinyglobby: 0.2.17 optionalDependencies: - '@types/node': 26.6.3 + '@types/node': 26.6.4 fsevents: 2.3.3 - vitest@5.0.2(@types/node@26.6.3)(@vitest/coverage-v8@5.0.2)(vite@8.2.1(@types/node@26.6.3)): + vitest@5.0.3(@types/node@26.6.4)(@vitest/coverage-v8@5.0.3)(vite@8.2.1(@types/node@26.6.4)): dependencies: '@types/chai': 5.2.3 - '@vitest/mocker': 5.0.2(vite@8.2.1(@types/node@26.6.3)) + '@vitest/mocker': 5.0.3(vite@8.2.1(@types/node@26.6.4)) chai: 6.2.2 es-module-lexer: 2.3.2 expect-type: 1.4.0 @@ -1727,11 +1721,11 @@ snapshots: tinybench: 6.1.4 tinyexec: 1.3.0 tinyglobby: 0.2.17 - vite: 8.2.1(@types/node@26.6.3) - why-is-node-running: 3.2.2 + vite: 8.2.1(@types/node@26.6.4) + why-is-node-running: 3.2.1 optionalDependencies: - '@types/node': 26.6.3 - '@vitest/coverage-v8': 5.0.2(vitest@5.0.2) + '@types/node': 26.6.4 + '@vitest/coverage-v8': 5.0.3(vitest@5.0.3) transitivePeerDependencies: - msw @@ -1739,7 +1733,7 @@ snapshots: dependencies: isexe: 2.0.0 - why-is-node-running@3.2.2: {} + why-is-node-running@3.2.1: {} word-wrap@1.2.5: {} diff --git a/web_ui/package.json b/web_ui/package.json index 9985862..7611e61 100644 --- a/web_ui/package.json +++ b/web_ui/package.json @@ -9,7 +9,7 @@ "node": ">=24", "pnpm": ">=10" }, - "packageManager": "pnpm@12.6.0+sha512.3ef68f951cb111ac204b4a5a16f0b2ddf0da56a96e0413e81d855d9f0b55ef926714709028e1cd00c405c2c5fb7b9e8ec4dc46777c805d0373c2f2ff00fd20ec", + "packageManager": "pnpm@12.9.0+sha512.8f64efa0b792986ca98ae568791bcd7722807e274c4f0c1d9853c62116495c584119db98762a2a5343b1684d361b9ae26fdd16d551666d7635346d3fae830ddd", "files": [ "dist" ], @@ -38,16 +38,16 @@ "@testing-library/jest-dom": "7.0.1", "@testing-library/react": "16.3.3", "@testing-library/user-event": "14.6.7", - "@types/node": "26.6.3", + "@types/node": "26.6.4", "@types/react": "19.3.0", "@types/react-dom": "19.3.0", - "@vitest/coverage-v8": "5.0.2", - "eslint": "10.11.0", + "@vitest/coverage-v8": "5.0.3", + "eslint": "10.12.0", "jsdom": "30.1.1", "react": "19.3.0", "react-dom": "19.3.0", "typescript": "6.0.3", - "typescript-eslint": "8.70.0", - "vitest": "5.0.2" + "typescript-eslint": "8.71.0", + "vitest": "5.0.3" } } diff --git a/web_ui/pnpm-lock.yaml b/web_ui/pnpm-lock.yaml index 4dedf94..514a307 100644 --- a/web_ui/pnpm-lock.yaml +++ b/web_ui/pnpm-lock.yaml @@ -7,153 +7,153 @@ importers: configDependencies: {} packageManagerDependencies: pnpm: - specifier: 12.6.0 - version: 12.6.0 + specifier: 12.9.0 + version: 12.9.0 packages: - '@pnpm/exe.android-arm64@12.6.0': - resolution: {integrity: sha512-kviIHft9h02q+7N2In7tSL9T/HRUSGwYmU74YPtsWU5ee7vgqPyC0JT6RRX6miW+NxX1CvHgQxg69qq1qTLJYQ==} + '@pnpm/exe.android-arm64@12.9.0': + resolution: {integrity: sha512-/53fHFwP1mBYQjli6h3LBZsocAMjrEY6hCDiomIwdLIFbtxHrWucPg/s8ozOG3K6Qt8yEA78v7fCcNrgZKTlEQ==} cpu: [arm64] os: [android] - '@pnpm/exe.android-x64@12.6.0': - resolution: {integrity: sha512-CT8aJKLq2mtZFE71pr4E5Z2xHL8uGnRGr+NclDqsXlVF4SVcQ2QAs14mWi0C8gjT4hbmi+Q0lJRRIkMAWfjjng==} + '@pnpm/exe.android-x64@12.9.0': + resolution: {integrity: sha512-QI+Md3q3Df0WbDGpqciRw9ge0r7CGbxSlP3HcyIf34/YdZsbmUTl416oWOe3RrkZpVVVCA20f8B9esYrITpArA==} cpu: [x64] os: [android] - '@pnpm/exe.darwin-arm64@12.6.0': - resolution: {integrity: sha512-rafpVkjzugKBMxSvGQd5wK1x5eOTkD/+lcJIFHeQhvd+BJ8kDisOOvwhQDrpGd4vd2Fx+hhW0P2Ptt638OlhFg==} + '@pnpm/exe.darwin-arm64@12.9.0': + resolution: {integrity: sha512-mWr7fGPYJ12GuvU/q0SyJfj7X/9Eqa5dA6cc+yUUAKRdYEcqW4lQcsDd1ArfxJNSDYooz9X+UhFeVxKO9e/xDA==} cpu: [arm64] os: [darwin] - '@pnpm/exe.darwin-x64@12.6.0': - resolution: {integrity: sha512-72Jpuv1m24gI8zUNcaylYpBWEhBzz1VPhnDIU2GYM2eRP+KsOqVLwV78i+JrihI39zY2NNXmaN4eAKX1inpaDQ==} + '@pnpm/exe.darwin-x64@12.9.0': + resolution: {integrity: sha512-fr4ns4FyMLptdYbWqW3yc/yvbfQP7D1RBCFUJpSPr7tOzd3JxZvl+Tkvg65kEAGU5wXI6vl3eCOofumEj/n3Gw==} cpu: [x64] os: [darwin] - '@pnpm/exe.freebsd-x64@12.6.0': - resolution: {integrity: sha512-LON4QgNy1w/XF4uySZIyC/5hEB0oGdelXsY9tSKqstER+2j2X2oHz67skD11RRd/HWFRtMr64shQZ4I9ZoZ0zg==} + '@pnpm/exe.freebsd-x64@12.9.0': + resolution: {integrity: sha512-4+hZPeJzSSw1oOrfUg+v4QylHv6qKhdgcCnKt9ooryE79p1Z9oyozVJ5em+g6ajbvv6jlH+epnht8/s9Cp5M3Q==} cpu: [x64] os: [freebsd] - '@pnpm/exe.linux-arm64-musl@12.6.0': - resolution: {integrity: sha512-BdDpX+DeaMUc5x6WNcE6FkmFy36NwrBbBvwsZU737Zt3u1fTY+iKFPchjEQNCM4rT6q6xvF2oN3ZwExGsJCkxQ==} + '@pnpm/exe.linux-arm64-musl@12.9.0': + resolution: {integrity: sha512-+mHQSbA6M6W71PyV0IxYu2T43lZA8rAhlw4Gy7EMlyiv8H2GWJAXtUXl2huw7tnsar2AhCRlPn/ZvUv+/mIBkA==} cpu: [arm64] os: [linux] libc: [musl] - '@pnpm/exe.linux-arm64@12.6.0': - resolution: {integrity: sha512-8h2sNoIhHDpHDYqZZCh9PXr6krqW10btb0GHVGuw710muDHlFAHMtUG2ogmaHge6zJG1xgY4Y7mwX5g/LWvrYg==} + '@pnpm/exe.linux-arm64@12.9.0': + resolution: {integrity: sha512-U1mBoG27+J/HuWYtVZOQrvbiKnTlDsDVzyroKQqHsJaTHTXi0H5Y+fU1GjUyMcNG5r/EXtbhp9iAwQt64uXTxQ==} cpu: [arm64] os: [linux] libc: [glibc] - '@pnpm/exe.linux-ppc64@12.6.0': - resolution: {integrity: sha512-ld6xgcEhFCFrsVsJhbmdmR714PnsEspO/Ij8w0/D7OSiz2hYGu3fZd5tKRxOI5m6H6Gl0/RRFAPncTHbsgXVjQ==} + '@pnpm/exe.linux-ppc64@12.9.0': + resolution: {integrity: sha512-P5bgei3i6wjlKq6s4PNg/0cvik354upY2wW3YGSfweYTABnAZP2zGzgMc0eO5SHTz934Cbya8Z0YFRdw6MfmCg==} cpu: [ppc64] os: [linux] libc: [glibc] - '@pnpm/exe.linux-riscv64@12.6.0': - resolution: {integrity: sha512-HIbImydoC3J8NFt+8MXfhTCf5rO6NjCMyinSAHac0VmV8uCHw+kbQkyAzxV84rzz9ZOzmBOnj0b1N461UIYGsw==} + '@pnpm/exe.linux-riscv64@12.9.0': + resolution: {integrity: sha512-oNx2uOo2gVrRckEvz7V+FKeq9HOF0rKNHcCyxRRHSXEXpmP6ZMMgJ+iU/m/zfSGp6bAcC5mvAE270FhAeIeKhA==} cpu: [riscv64] os: [linux] libc: [glibc] - '@pnpm/exe.linux-s390x@12.6.0': - resolution: {integrity: sha512-DjSqT7+BZ/lWcDHwNMuzsRVfyxUimh+D54Xw4tSWbdVVQhtWtNMUyI93LVnwCmsoEpar2gC3SFeAgywx37svKQ==} + '@pnpm/exe.linux-s390x@12.9.0': + resolution: {integrity: sha512-OChHS7hOlqdBMtZqjIB1QoZGNfzLPSsvTa2kYanf3qso5Q+KgnROQ+uV4a0wRGC6sdshSA63+Ui8MCylRy6iwA==} cpu: [s390x] os: [linux] libc: [glibc] - '@pnpm/exe.linux-x64-musl@12.6.0': - resolution: {integrity: sha512-31lKeGPmRE6xfV6I3VjaEGVFRei14pl/zgoFDK4w+UkbJqOuNl2Ht3O1GuqNIg3Gl14qTEb4kZRAYFHXifDx9A==} + '@pnpm/exe.linux-x64-musl@12.9.0': + resolution: {integrity: sha512-ubZPsDMXS+LEnPa7wZD18E+KTpOAZPt+ms/khfIROt+6Cp0A2kayQA8aIsvV7+jGZD0xoknvaGN/P3v9OisZUg==} cpu: [x64] os: [linux] libc: [musl] - '@pnpm/exe.linux-x64@12.6.0': - resolution: {integrity: sha512-qFWBneHJAJ73W4whtbaFOL1M/7DBC6ILHXuxc7ZPtEhfPuT1zeZiGrmKHoMAfJA+mcm6xhOFljqVTUS+00Jabw==} + '@pnpm/exe.linux-x64@12.9.0': + resolution: {integrity: sha512-ElANYUwQuMfWqMJR4m5L5h9zKjvGKcA8hi2put4J1DMx2smVaNGkGnxkgeIYgMjmF7UFxlZDcL4jItkScgsW4A==} cpu: [x64] os: [linux] libc: [glibc] - '@pnpm/exe.win32-arm64@12.6.0': - resolution: {integrity: sha512-OhfefXEEykZlslSUhR8PPRe6MVPV3Oink/dVfIecE5velpc7j80cZYsHlaDrtRDcm3TPwQHkaRW1SJ1efX9oFg==} + '@pnpm/exe.win32-arm64@12.9.0': + resolution: {integrity: sha512-cqnBKZg5e9fFghmYtgJLyZOAsS1EyLeDVJ0qNsjR7hcc+dq1mWRiDo0erqdj2PGoYaVogyF9WejZSJoKvlfXDw==} cpu: [arm64] os: [win32] - '@pnpm/exe.win32-x64@12.6.0': - resolution: {integrity: sha512-L2tuyrD2+Imgxs3VK/ST/2L3xD6vDP0ktJQxM0TaGw7TbAQEMq+RMc8iK6Ew7Id579uzjJe8jSfJID2ZGsOHCA==} + '@pnpm/exe.win32-x64@12.9.0': + resolution: {integrity: sha512-khqst6QGgHdKs2lEmz0N75qN/EoOcEywNQlRPfBrBrq1SytHra6ro/xViueJkVvDOz9gEO8oYcXTdZeAjErjMg==} cpu: [x64] os: [win32] - pnpm@12.6.0: - resolution: {integrity: sha512-PvaPlRyxEawgS0paFvCy3fDaVqluBBPoHYVdnwtV75JnFHCQKOHNAMQFwsX7e56OxNxGd3yAXQNzwvL/AP0g7A==} + pnpm@12.9.0: + resolution: {integrity: sha512-j2TvoLeSmGypiuVoeRvNdyKAfidMTwwdmFPGIRZJXFhBGduYdioqU0OxaE02G5rib90W1VFmbXY1NG0/roMN3Q==} engines: {node: '>=18.*'} hasBin: true snapshots: - '@pnpm/exe.android-arm64@12.6.0': + '@pnpm/exe.android-arm64@12.9.0': optional: true - '@pnpm/exe.android-x64@12.6.0': + '@pnpm/exe.android-x64@12.9.0': optional: true - '@pnpm/exe.darwin-arm64@12.6.0': + '@pnpm/exe.darwin-arm64@12.9.0': optional: true - '@pnpm/exe.darwin-x64@12.6.0': + '@pnpm/exe.darwin-x64@12.9.0': optional: true - '@pnpm/exe.freebsd-x64@12.6.0': + '@pnpm/exe.freebsd-x64@12.9.0': optional: true - '@pnpm/exe.linux-arm64-musl@12.6.0': + '@pnpm/exe.linux-arm64-musl@12.9.0': optional: true - '@pnpm/exe.linux-arm64@12.6.0': + '@pnpm/exe.linux-arm64@12.9.0': optional: true - '@pnpm/exe.linux-ppc64@12.6.0': + '@pnpm/exe.linux-ppc64@12.9.0': optional: true - '@pnpm/exe.linux-riscv64@12.6.0': + '@pnpm/exe.linux-riscv64@12.9.0': optional: true - '@pnpm/exe.linux-s390x@12.6.0': + '@pnpm/exe.linux-s390x@12.9.0': optional: true - '@pnpm/exe.linux-x64-musl@12.6.0': + '@pnpm/exe.linux-x64-musl@12.9.0': optional: true - '@pnpm/exe.linux-x64@12.6.0': + '@pnpm/exe.linux-x64@12.9.0': optional: true - '@pnpm/exe.win32-arm64@12.6.0': + '@pnpm/exe.win32-arm64@12.9.0': optional: true - '@pnpm/exe.win32-x64@12.6.0': + '@pnpm/exe.win32-x64@12.9.0': optional: true - pnpm@12.6.0: + pnpm@12.9.0: optionalDependencies: - '@pnpm/exe.android-arm64': 12.6.0 - '@pnpm/exe.android-x64': 12.6.0 - '@pnpm/exe.darwin-arm64': 12.6.0 - '@pnpm/exe.darwin-x64': 12.6.0 - '@pnpm/exe.freebsd-x64': 12.6.0 - '@pnpm/exe.linux-arm64': 12.6.0 - '@pnpm/exe.linux-arm64-musl': 12.6.0 - '@pnpm/exe.linux-ppc64': 12.6.0 - '@pnpm/exe.linux-riscv64': 12.6.0 - '@pnpm/exe.linux-s390x': 12.6.0 - '@pnpm/exe.linux-x64': 12.6.0 - '@pnpm/exe.linux-x64-musl': 12.6.0 - '@pnpm/exe.win32-arm64': 12.6.0 - '@pnpm/exe.win32-x64': 12.6.0 + '@pnpm/exe.android-arm64': 12.9.0 + '@pnpm/exe.android-x64': 12.9.0 + '@pnpm/exe.darwin-arm64': 12.9.0 + '@pnpm/exe.darwin-x64': 12.9.0 + '@pnpm/exe.freebsd-x64': 12.9.0 + '@pnpm/exe.linux-arm64': 12.9.0 + '@pnpm/exe.linux-arm64-musl': 12.9.0 + '@pnpm/exe.linux-ppc64': 12.9.0 + '@pnpm/exe.linux-riscv64': 12.9.0 + '@pnpm/exe.linux-s390x': 12.9.0 + '@pnpm/exe.linux-x64': 12.9.0 + '@pnpm/exe.linux-x64-musl': 12.9.0 + '@pnpm/exe.win32-arm64': 12.9.0 + '@pnpm/exe.win32-x64': 12.9.0 --- lockfileVersion: '9.0' @@ -168,13 +168,13 @@ importers: devDependencies: '@eslint/js': specifier: 10.0.1 - version: 10.0.1(eslint@10.11.0(supports-color@7.2.0)) + version: 10.0.1(eslint@10.12.0(supports-color@7.2.0)) '@kuhyx/ts-config': specifier: link:../ts_config version: link:../ts_config '@testing-library/jest-dom': specifier: 7.0.1 - version: 7.0.1(@testing-library/dom@10.4.1)(vitest@5.0.2) + version: 7.0.1(@testing-library/dom@10.4.1)(vitest@5.0.3) '@testing-library/react': specifier: 16.3.3 version: 16.3.3(@testing-library/dom@10.4.1)(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) @@ -182,8 +182,8 @@ importers: specifier: 14.6.7 version: 14.6.7(@testing-library/dom@10.4.1) '@types/node': - specifier: 26.6.3 - version: 26.6.3 + specifier: 26.6.4 + version: 26.6.4 '@types/react': specifier: 19.3.0 version: 19.3.0 @@ -191,11 +191,11 @@ importers: specifier: 19.3.0 version: 19.3.0(@types/react@19.3.0) '@vitest/coverage-v8': - specifier: 5.0.2 - version: 5.0.2(vitest@5.0.2) + specifier: 5.0.3 + version: 5.0.3(vitest@5.0.3) eslint: - specifier: 10.11.0 - version: 10.11.0(supports-color@7.2.0) + specifier: 10.12.0 + version: 10.12.0(supports-color@7.2.0) jsdom: specifier: 30.1.1 version: 30.1.1 @@ -209,11 +209,11 @@ importers: specifier: 6.0.3 version: 6.0.3 typescript-eslint: - specifier: 8.70.0 - version: 8.70.0(eslint@10.11.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) + specifier: 8.71.0 + version: 8.71.0(eslint@10.12.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) vitest: - specifier: 5.0.2 - version: 5.0.2(@types/node@26.6.3)(@vitest/coverage-v8@5.0.2)(jsdom@30.1.1)(vite@8.2.2(@types/node@26.6.3)) + specifier: 5.0.3 + version: 5.0.3(@types/node@26.6.4)(@vitest/coverage-v8@5.0.3)(jsdom@30.1.1)(vite@8.2.2(@types/node@26.6.4)) packages: @@ -545,8 +545,8 @@ packages: '@types/json-schema@7.0.15': resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} - '@types/node@26.6.3': - resolution: {integrity: sha512-dsqMQQoeTLqu9wynDD00q573mNzso3IdQOAfHRJqLCcmCFPoGo9A1bDpUcv/9tnKpErQWv9uKeGfl37EIS02Yg==} + '@types/node@26.6.4': + resolution: {integrity: sha512-ldVPDCzj7fsaGZrLB0NuHuTvJcsNasysBAqMolr/cgxrLd1xbqxIr3XJiPnHHJUCxj5sNF1vnRj9aWnrVh5Jcg==} '@types/react-dom@19.3.0': resolution: {integrity: sha512-ZI7bU42mZXXKHn/qNLEw2IrbiINU7X5+vfgdixBHkCNpYWXjKgfQ/P+uyGb5CjOLB9UcnTeg3rylQtV2hym44Q==} @@ -556,70 +556,70 @@ packages: '@types/react@19.3.0': resolution: {integrity: sha512-N0rFCuH9YoxG9/m61l9MfpJKfmLOVU0em7ipIz6TRgSSkvReLB9vL85GB+yr8Bs5leqpvg96JSwF4ZS1s4viQg==} - '@typescript-eslint/eslint-plugin@8.70.0': - resolution: {integrity: sha512-/v8HZt6RlyIZxB3ntehELOcUcfxKPVGWXnQdJuHRmzrqgF8nQypcC/oxGW+Ot4VGKDq81XugPKxx0n5PBtf9PA==} + '@typescript-eslint/eslint-plugin@8.71.0': + resolution: {integrity: sha512-pqcS9c1HxZTHt7End4nXqd0s5lJrrFzrgCkKFJrsbUnaL6M3+6oBFZaslg6Gjsl3argl2DDRFROnXARaZ2e4Nw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - '@typescript-eslint/parser': ^8.70.0 + '@typescript-eslint/parser': ^8.71.0 eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/parser@8.70.0': - resolution: {integrity: sha512-zYvrmj9Yxd63UGaXw+kdt6A0F0s0qveJyuatIM77bYC2DE4pgmg7a50u8LR7PRtXd0x+h+Tl3eXabGm06SWd3Q==} + '@typescript-eslint/parser@8.71.0': + resolution: {integrity: sha512-CG4nPk1f2zc8yw4pALqHsFYH2hdo+h1T9daSp21+Hnxi9LOE3GT9hAfTKJCBXVNM2GmYs1eMEP615wPoeOgk3A==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/project-service@8.70.0': - resolution: {integrity: sha512-hFHbTNqhU9G+2eKFXCBVb1tjFT/LceiJ4+HfLO4pTpDI0KHi6iajpcFFkaSQ9gXmCh7n82A0PthaayEdN6mspQ==} + '@typescript-eslint/project-service@8.71.0': + resolution: {integrity: sha512-aABjw5rjBacYONVPaPiWOCjJu0vEF4a25iQuodlmQYL1trtLZ0X/y+2Vzl3BKI1odM4LnwLE1oUDXYp1wzx1TQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/scope-manager@8.70.0': - resolution: {integrity: sha512-8nP3Kwh5hlgZ4FicGvmznAmJe8UL4sdU8tLukrPaMuQmDuk4Y8xYfzu/aYZW4xT2JCgc7H/TpDI5cGlxcWJSqQ==} + '@typescript-eslint/scope-manager@8.71.0': + resolution: {integrity: sha512-gWF0BhUcnjZxSpLE8ngS/59n2SB0J3YqRxvX1+2aoRJk9hNtHSLOV+TcarFiOr5ipXm3yc1QrI4c9YZc8zyCxw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/tsconfig-utils@8.70.0': - resolution: {integrity: sha512-adnkeeNq9Sq1sUf4+FRVc0KdgYghzsgFpZSQVZVvY0LCuUuN0FnQgyGzCJeC4fW1cdXseBAjU2EOqUIjbNcZUw==} + '@typescript-eslint/tsconfig-utils@8.71.0': + resolution: {integrity: sha512-Z1UlWHADEK2Mlb9NpWfDeSjqoZ5EyrOv4R3eQpbkzqn/EwaIdOpXXupEA1+0ZIOSJSZZDBHG0BrQyN8zUG6Pwg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/type-utils@8.70.0': - resolution: {integrity: sha512-NUMKIhYVaVIVLnRL9CRt+VVcuLgSHUCpXn4/+K8wql+vdInUzvx8BjUO1oJ7cG9shjFJKtF8F8Hh2kCh3/KBVw==} + '@typescript-eslint/type-utils@8.71.0': + resolution: {integrity: sha512-i8uO1qbdxeKgRnS5sCRt6On3/nfo2d2DwQe3Yvjx543zLy7r8ySqRuPPiIIXAhS03U0v5NfAFx+rUgxFzKKwNw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/types@8.70.0': - resolution: {integrity: sha512-asTOIYhDg4zdzOScCyaytrsV3cR6B4ecPQlXw/dJIm7J/MZTtCtfVII9JD8Geh4jTCrK/Xe6cg5UevoleMcoJQ==} + '@typescript-eslint/types@8.71.0': + resolution: {integrity: sha512-cJ4OoxPGWvFnBTnSZyaU+qJzGTqPTGJY+gDchj6cRyLRdmIdt4rcsE4twj+zPfrNiWuVi38wijHzShL++Z9atQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/typescript-estree@8.70.0': - resolution: {integrity: sha512-d9NmHMPEKQ7QCLLm1jI3zmoQBwT5KwFYjXBJ9ymZfKCUU+5rmTRykKAFvH5Qn/ZCds3CEAFS9OC9M/jkl0X2bA==} + '@typescript-eslint/typescript-estree@8.71.0': + resolution: {integrity: sha512-PEEF4G5sLLWAS5BpPrUvms4ySZkiBQQZM4z+3ReI46axK5Vqr/vXBQatJQIZZOYdGyPUAKTtsrWzpqKuU+3DEw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/utils@8.70.0': - resolution: {integrity: sha512-oZmtKJz/4fufZ2p3+Cn3ijEojcdfR+1zYDH2xKYrEly0dR/Q/1xUPRCOlKGxod78nWlU2UnDe09GZ3TaknBFGA==} + '@typescript-eslint/utils@8.71.0': + resolution: {integrity: sha512-pKR/tEMVrXZG23UFKUn5BQf3zfmfk7KQceI2cGzywZ5nxM5Eu3hEJU1utjWzydtzBbcJAQhHN8iPCxobHpPcZQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/visitor-keys@8.70.0': - resolution: {integrity: sha512-BoC8PiO4Hkdo0TVJh9Ntxr5MxPDI7/oFsrygN5ADelFSeXG/qgNuucIGA+L5Z6JpPTE/uRfcTWtscjbUaufepQ==} + '@typescript-eslint/visitor-keys@8.71.0': + resolution: {integrity: sha512-8eQ9R218XORK+KLosnf4bu/QsUXvUyVwTbArg7/0NMB1Pu87OJKvj4nhFblkYE8gQV73mW1dx1ptlPCkwRGa7A==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@vitest/coverage-v8@5.0.2': - resolution: {integrity: sha512-3ffHBEi8DOOBLwIGBhOBZbRfYFYWjMUuxZicONdhuFEFEG5AVsMOSrVeuROskqnqpbOmEJwxM2eTVZ9N3a1t+A==} + '@vitest/coverage-v8@5.0.3': + resolution: {integrity: sha512-+klsyz7BvT1vCU28Zkfzms1Ia78XD0V41U3FUtRaa3S+vOr/EXvK1O6BvVD7l1RzEjm6SONR2aybOvDDf9u0mQ==} peerDependencies: - '@vitest/browser': 5.0.2 - vitest: 5.0.2 + '@vitest/browser': 5.0.3 + vitest: 5.0.3 peerDependenciesMeta: '@vitest/browser': optional: true @@ -632,8 +632,8 @@ packages: resolution: {integrity: sha512-1EOLRfsTMnyAr3+kEAsP4o9dhaDlGPpD7H5iLBBeq//YpNB1VIahkPhB+eRp9N2Dkfw8oySROjE3yf9XDeaIkQ==} engines: {node: '>=22'} - '@vitest/mocker@5.0.2': - resolution: {integrity: sha512-Z5FS00Q1SJHkB35xATsmWGdQ5WA1/0MV3CDjqyv7GavHv1OfOj145MNfHOlHk7QLes21dKFDHr8EO2zvL+9WGA==} + '@vitest/mocker@5.0.3': + resolution: {integrity: sha512-T8sWAIbkSyAjkwTcaEc3Iu0o9A27X1/kdXrizhZkGuSKScRQtRzclfAMpOTcGdXCsqxeWlpGy3XjqaW8CpLORg==} peerDependencies: msw: ^2.4.9 vite: ^6.0.0 || ^7.0.0 || ^8.0.0 @@ -643,8 +643,8 @@ packages: vite: optional: true - '@vitest/spy@5.0.2': - resolution: {integrity: sha512-Ijc7T1nT9efNb5LxvjaBrEqw3f/QwUv5EE0nKqZxgqsaV/FxAAZ8baGylA8X/Z2oS4Lp+K74Jr6dTJsDKxJDeg==} + '@vitest/spy@5.0.3': + resolution: {integrity: sha512-XhFysQTB8AZ+P4gMi+Lpo99vg2AZi0qKpaB9yXQl37+CaMEAPO3iH/wGVnSyL5MPERiLezpqTVtrR6UZH5GCXg==} acorn-jsx@5.3.2: resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} @@ -769,8 +769,8 @@ packages: resolution: {integrity: sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==} engines: {node: ^20.19.0 || ^22.13.0 || >=24} - eslint@10.11.0: - resolution: {integrity: sha512-P7a6UEEqb9G95MYAtqkmsTbVXIYyzIfl6NGOIJk162PaahFxFyeGcrlXYFSiagECg4sEm8IseJdZBKR3rx6MsQ==} + eslint@10.12.0: + resolution: {integrity: sha512-npHjrHb2o4ehH3mE+YuxABR+0gyb0aVWCIolgJDArwCiHEIsonBm3ZKjel5b0U5+a+MbEvIyRCGCzD3eKKK2NA==} engines: {node: ^20.19.0 || ^22.13.0 || >=24} hasBin: true peerDependencies: @@ -1195,8 +1195,8 @@ packages: resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} engines: {node: '>= 0.8.0'} - typescript-eslint@8.70.0: - resolution: {integrity: sha512-P/W5cz70/cQAuKfY3xwQMWWTV7BvJ0mAQmi+9mBcsVPaBUpd6Ohpa+fECv9rBFrQcig86jAiNBFNWUqnTjr4pw==} + typescript-eslint@8.71.0: + resolution: {integrity: sha512-fBdHYiqQ14RW6mOMXD14Svn82ZsCYAoQSzGRzyEjR59S5A2Krh/l7fGTOQ7iCr8gGy/mHVXtEF7s5fgjEdV0Pw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 @@ -1260,20 +1260,20 @@ packages: yaml: optional: true - vitest@5.0.2: - resolution: {integrity: sha512-7MQrx9pDv5aHiUcovIb/70Ys3tgtkUVgCtledvKdCmEO+/1Dicq5ZqoSxOW034m03oqC+oHOKui2dM6qtMLoJg==} + vitest@5.0.3: + resolution: {integrity: sha512-xMw97S3rjdtj5dkVat7jCsqWBpvchs3RlpQctUqwJD0KkERk40vz2fJ77lDwW/Vzh/pk18eItYAzkodhSes3jQ==} engines: {node: ^22.12.0 || ^24.0.0 || >=26.0.0} hasBin: true peerDependencies: '@edge-runtime/vm': '*' '@opentelemetry/api': ^1.9.0 '@types/node': ^22.0.0 || >=24.0.0 - '@vitest/browser-playwright': 5.0.2 - '@vitest/browser-preview': 5.0.2 + '@vitest/browser-playwright': 5.0.3 + '@vitest/browser-preview': 5.0.3 '@vitest/browser-webdriverio': ^5.0.0-beta.5 || >=5.0.0 - '@vitest/coverage-istanbul': 5.0.2 - '@vitest/coverage-v8': 5.0.2 - '@vitest/ui': 5.0.2 + '@vitest/coverage-istanbul': 5.0.3 + '@vitest/coverage-v8': 5.0.3 + '@vitest/ui': 5.0.3 happy-dom: '*' jsdom: '*' vite: ^6.4.0 || ^7.0.0 || ^8.0.0 @@ -1326,8 +1326,8 @@ packages: engines: {node: '>= 8'} hasBin: true - why-is-node-running@3.2.2: - resolution: {integrity: sha512-NKUzAelcoCXhXL4dJzKIwXeR8iEVqsA0Lq6Vnd0UXvgaKbzVo4ZTHROF2Jidrv+SgxOQ03fMinnNhzZATxOD3A==} + why-is-node-running@3.2.1: + resolution: {integrity: sha512-Tb2FUhB4vUsGQlfSquQLYkApkuPAFQXGFzxWKHHumVz2dK+X1RUm/HnID4+TfIGYJ1kTcwOaCk/buYCEJr6YjQ==} engines: {node: '>=20.11'} hasBin: true @@ -1428,9 +1428,9 @@ snapshots: '@csstools/css-tokenizer@4.0.0': {} - '@eslint-community/eslint-utils@4.10.1(eslint@10.11.0(supports-color@7.2.0))': + '@eslint-community/eslint-utils@4.10.1(eslint@10.12.0(supports-color@7.2.0))': dependencies: - eslint: 10.11.0(supports-color@7.2.0) + eslint: 10.12.0(supports-color@7.2.0) eslint-visitor-keys: 3.4.3 '@eslint-community/regexpp@4.12.2': {} @@ -1451,9 +1451,9 @@ snapshots: dependencies: '@types/json-schema': 7.0.15 - '@eslint/js@10.0.1(eslint@10.11.0(supports-color@7.2.0))': + '@eslint/js@10.0.1(eslint@10.12.0(supports-color@7.2.0))': optionalDependencies: - eslint: 10.11.0(supports-color@7.2.0) + eslint: 10.12.0(supports-color@7.2.0) '@eslint/object-schema@3.0.5': {} @@ -1557,7 +1557,7 @@ snapshots: picocolors: 1.1.1 pretty-format: 27.5.1 - '@testing-library/jest-dom@7.0.1(@testing-library/dom@10.4.1)(vitest@5.0.2)': + '@testing-library/jest-dom@7.0.1(@testing-library/dom@10.4.1)(vitest@5.0.3)': dependencies: '@adobe/css-tools': 4.5.0 '@testing-library/dom': 10.4.1 @@ -1567,7 +1567,7 @@ snapshots: picocolors: 1.1.1 redent: 3.0.0 optionalDependencies: - vitest: 5.0.2(@types/node@26.6.3)(@vitest/coverage-v8@5.0.2)(jsdom@30.1.1)(vite@8.2.2(@types/node@26.6.3)) + vitest: 5.0.3(@types/node@26.6.4)(@vitest/coverage-v8@5.0.3)(jsdom@30.1.1)(vite@8.2.2(@types/node@26.6.4)) '@testing-library/react@16.3.3(@testing-library/dom@10.4.1)(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)': dependencies: @@ -1598,7 +1598,7 @@ snapshots: '@types/json-schema@7.0.15': {} - '@types/node@26.6.3': + '@types/node@26.6.4': dependencies: undici-types: 8.9.0 @@ -1610,15 +1610,15 @@ snapshots: dependencies: csstype: 3.2.3 - '@typescript-eslint/eslint-plugin@8.70.0(@typescript-eslint/parser@8.70.0(eslint@10.11.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3))(eslint@10.11.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3)': + '@typescript-eslint/eslint-plugin@8.71.0(@typescript-eslint/parser@8.71.0(eslint@10.12.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3))(eslint@10.12.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3)': dependencies: '@eslint-community/regexpp': 4.12.2 - '@typescript-eslint/parser': 8.70.0(eslint@10.11.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) - '@typescript-eslint/scope-manager': 8.70.0 - '@typescript-eslint/type-utils': 8.70.0(eslint@10.11.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) - '@typescript-eslint/utils': 8.70.0(eslint@10.11.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) - '@typescript-eslint/visitor-keys': 8.70.0 - eslint: 10.11.0(supports-color@7.2.0) + '@typescript-eslint/parser': 8.71.0(eslint@10.12.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) + '@typescript-eslint/scope-manager': 8.71.0 + '@typescript-eslint/type-utils': 8.71.0(eslint@10.12.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) + '@typescript-eslint/utils': 8.71.0(eslint@10.12.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) + '@typescript-eslint/visitor-keys': 8.71.0 + eslint: 10.12.0(supports-color@7.2.0) ignore: 7.0.6 natural-compare: 1.4.0 ts-api-utils: 2.5.0(typescript@6.0.3) @@ -1626,56 +1626,56 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/parser@8.70.0(eslint@10.11.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3)': + '@typescript-eslint/parser@8.71.0(eslint@10.12.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3)': dependencies: - '@typescript-eslint/scope-manager': 8.70.0 - '@typescript-eslint/types': 8.70.0 - '@typescript-eslint/typescript-estree': 8.70.0(supports-color@7.2.0)(typescript@6.0.3) - '@typescript-eslint/visitor-keys': 8.70.0 + '@typescript-eslint/scope-manager': 8.71.0 + '@typescript-eslint/types': 8.71.0 + '@typescript-eslint/typescript-estree': 8.71.0(supports-color@7.2.0)(typescript@6.0.3) + '@typescript-eslint/visitor-keys': 8.71.0 debug: 4.4.3(supports-color@7.2.0) - eslint: 10.11.0(supports-color@7.2.0) + eslint: 10.12.0(supports-color@7.2.0) typescript: 6.0.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/project-service@8.70.0(supports-color@7.2.0)(typescript@6.0.3)': + '@typescript-eslint/project-service@8.71.0(supports-color@7.2.0)(typescript@6.0.3)': dependencies: - '@typescript-eslint/tsconfig-utils': 8.70.0(typescript@6.0.3) - '@typescript-eslint/types': 8.70.0 + '@typescript-eslint/tsconfig-utils': 8.71.0(typescript@6.0.3) + '@typescript-eslint/types': 8.71.0 debug: 4.4.3(supports-color@7.2.0) typescript: 6.0.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/scope-manager@8.70.0': + '@typescript-eslint/scope-manager@8.71.0': dependencies: - '@typescript-eslint/types': 8.70.0 - '@typescript-eslint/visitor-keys': 8.70.0 + '@typescript-eslint/types': 8.71.0 + '@typescript-eslint/visitor-keys': 8.71.0 - '@typescript-eslint/tsconfig-utils@8.70.0(typescript@6.0.3)': + '@typescript-eslint/tsconfig-utils@8.71.0(typescript@6.0.3)': dependencies: typescript: 6.0.3 - '@typescript-eslint/type-utils@8.70.0(eslint@10.11.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3)': + '@typescript-eslint/type-utils@8.71.0(eslint@10.12.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3)': dependencies: - '@typescript-eslint/types': 8.70.0 - '@typescript-eslint/typescript-estree': 8.70.0(supports-color@7.2.0)(typescript@6.0.3) - '@typescript-eslint/utils': 8.70.0(eslint@10.11.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) + '@typescript-eslint/types': 8.71.0 + '@typescript-eslint/typescript-estree': 8.71.0(supports-color@7.2.0)(typescript@6.0.3) + '@typescript-eslint/utils': 8.71.0(eslint@10.12.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) debug: 4.4.3(supports-color@7.2.0) - eslint: 10.11.0(supports-color@7.2.0) + eslint: 10.12.0(supports-color@7.2.0) ts-api-utils: 2.5.0(typescript@6.0.3) typescript: 6.0.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/types@8.70.0': {} + '@typescript-eslint/types@8.71.0': {} - '@typescript-eslint/typescript-estree@8.70.0(supports-color@7.2.0)(typescript@6.0.3)': + '@typescript-eslint/typescript-estree@8.71.0(supports-color@7.2.0)(typescript@6.0.3)': dependencies: - '@typescript-eslint/project-service': 8.70.0(supports-color@7.2.0)(typescript@6.0.3) - '@typescript-eslint/tsconfig-utils': 8.70.0(typescript@6.0.3) - '@typescript-eslint/types': 8.70.0 - '@typescript-eslint/visitor-keys': 8.70.0 + '@typescript-eslint/project-service': 8.71.0(supports-color@7.2.0)(typescript@6.0.3) + '@typescript-eslint/tsconfig-utils': 8.71.0(typescript@6.0.3) + '@typescript-eslint/types': 8.71.0 + '@typescript-eslint/visitor-keys': 8.71.0 debug: 4.4.3(supports-color@7.2.0) minimatch: 10.2.6 semver: 7.8.5 @@ -1685,23 +1685,23 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/utils@8.70.0(eslint@10.11.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3)': + '@typescript-eslint/utils@8.71.0(eslint@10.12.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3)': dependencies: - '@eslint-community/eslint-utils': 4.10.1(eslint@10.11.0(supports-color@7.2.0)) - '@typescript-eslint/scope-manager': 8.70.0 - '@typescript-eslint/types': 8.70.0 - '@typescript-eslint/typescript-estree': 8.70.0(supports-color@7.2.0)(typescript@6.0.3) - eslint: 10.11.0(supports-color@7.2.0) + '@eslint-community/eslint-utils': 4.10.1(eslint@10.12.0(supports-color@7.2.0)) + '@typescript-eslint/scope-manager': 8.71.0 + '@typescript-eslint/types': 8.71.0 + '@typescript-eslint/typescript-estree': 8.71.0(supports-color@7.2.0)(typescript@6.0.3) + eslint: 10.12.0(supports-color@7.2.0) typescript: 6.0.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/visitor-keys@8.70.0': + '@typescript-eslint/visitor-keys@8.71.0': dependencies: - '@typescript-eslint/types': 8.70.0 + '@typescript-eslint/types': 8.71.0 eslint-visitor-keys: 5.0.1 - '@vitest/coverage-v8@5.0.2(vitest@5.0.2)': + '@vitest/coverage-v8@5.0.3(vitest@5.0.3)': dependencies: '@bcoe/v8-coverage': 1.0.2 '@vitest/istanbul-lib-coverage': 1.0.1 @@ -1711,7 +1711,7 @@ snapshots: obug: 2.1.4 std-env: 4.2.0 tinyrainbow: 3.1.1 - vitest: 5.0.2(@types/node@26.6.3)(@vitest/coverage-v8@5.0.2)(jsdom@30.1.1)(vite@8.2.2(@types/node@26.6.3)) + vitest: 5.0.3(@types/node@26.6.4)(@vitest/coverage-v8@5.0.3)(jsdom@30.1.1)(vite@8.2.2(@types/node@26.6.4)) '@vitest/istanbul-lib-coverage@1.0.1': {} @@ -1719,16 +1719,16 @@ snapshots: dependencies: '@vitest/istanbul-lib-coverage': 1.0.1 - '@vitest/mocker@5.0.2(vite@8.2.2(@types/node@26.6.3))': + '@vitest/mocker@5.0.3(vite@8.2.2(@types/node@26.6.4))': dependencies: '@jridgewell/trace-mapping': 0.3.31 - '@vitest/spy': 5.0.2 + '@vitest/spy': 5.0.3 estree-walker: 3.0.3 magic-string: 1.3.1 optionalDependencies: - vite: 8.2.2(@types/node@26.6.3) + vite: 8.2.2(@types/node@26.6.4) - '@vitest/spy@5.0.2': {} + '@vitest/spy@5.0.3': {} acorn-jsx@5.3.2(acorn@8.18.0): dependencies: @@ -1838,9 +1838,9 @@ snapshots: eslint-visitor-keys@5.0.1: {} - eslint@10.11.0(supports-color@7.2.0): + eslint@10.12.0(supports-color@7.2.0): dependencies: - '@eslint-community/eslint-utils': 4.10.1(eslint@10.11.0(supports-color@7.2.0)) + '@eslint-community/eslint-utils': 4.10.1(eslint@10.12.0(supports-color@7.2.0)) '@eslint-community/regexpp': 4.12.2 '@eslint/config-array': 0.23.5(supports-color@7.2.0) '@eslint/config-helpers': 0.7.0 @@ -2239,13 +2239,13 @@ snapshots: dependencies: prelude-ls: 1.2.1 - typescript-eslint@8.70.0(eslint@10.11.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3): + typescript-eslint@8.71.0(eslint@10.12.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3): dependencies: - '@typescript-eslint/eslint-plugin': 8.70.0(@typescript-eslint/parser@8.70.0(eslint@10.11.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3))(eslint@10.11.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) - '@typescript-eslint/parser': 8.70.0(eslint@10.11.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) - '@typescript-eslint/typescript-estree': 8.70.0(supports-color@7.2.0)(typescript@6.0.3) - '@typescript-eslint/utils': 8.70.0(eslint@10.11.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) - eslint: 10.11.0(supports-color@7.2.0) + '@typescript-eslint/eslint-plugin': 8.71.0(@typescript-eslint/parser@8.71.0(eslint@10.12.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3))(eslint@10.12.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) + '@typescript-eslint/parser': 8.71.0(eslint@10.12.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) + '@typescript-eslint/typescript-estree': 8.71.0(supports-color@7.2.0)(typescript@6.0.3) + '@typescript-eslint/utils': 8.71.0(eslint@10.12.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) + eslint: 10.12.0(supports-color@7.2.0) typescript: 6.0.3 transitivePeerDependencies: - supports-color @@ -2260,7 +2260,7 @@ snapshots: dependencies: punycode: 2.3.1 - vite@8.2.2(@types/node@26.6.3): + vite@8.2.2(@types/node@26.6.4): dependencies: lightningcss: 1.33.0 picomatch: 4.0.7 @@ -2268,13 +2268,13 @@ snapshots: rolldown: 1.2.6 tinyglobby: 0.2.17 optionalDependencies: - '@types/node': 26.6.3 + '@types/node': 26.6.4 fsevents: 2.3.3 - vitest@5.0.2(@types/node@26.6.3)(@vitest/coverage-v8@5.0.2)(jsdom@30.1.1)(vite@8.2.2(@types/node@26.6.3)): + vitest@5.0.3(@types/node@26.6.4)(@vitest/coverage-v8@5.0.3)(jsdom@30.1.1)(vite@8.2.2(@types/node@26.6.4)): dependencies: '@types/chai': 5.2.3 - '@vitest/mocker': 5.0.2(vite@8.2.2(@types/node@26.6.3)) + '@vitest/mocker': 5.0.3(vite@8.2.2(@types/node@26.6.4)) chai: 6.2.2 es-module-lexer: 2.3.2 expect-type: 1.4.0 @@ -2285,11 +2285,11 @@ snapshots: tinybench: 6.1.4 tinyexec: 1.3.0 tinyglobby: 0.2.17 - vite: 8.2.2(@types/node@26.6.3) - why-is-node-running: 3.2.2 + vite: 8.2.2(@types/node@26.6.4) + why-is-node-running: 3.2.1 optionalDependencies: - '@types/node': 26.6.3 - '@vitest/coverage-v8': 5.0.2(vitest@5.0.2) + '@types/node': 26.6.4 + '@vitest/coverage-v8': 5.0.3(vitest@5.0.3) jsdom: 30.1.1 transitivePeerDependencies: - msw @@ -2322,7 +2322,7 @@ snapshots: dependencies: isexe: 2.0.0 - why-is-node-running@3.2.2: {} + why-is-node-running@3.2.1: {} word-wrap@1.2.5: {}