mirror of
https://github.com/kuhyx/utils.git
synced 2026-11-10 17:47:24 +01:00
One Python per repo, always the newest, and the gate that enforces it
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
5fc792d791
commit
3565b281ca
2
.github/workflows/crdt-sync-precommit.yml
vendored
2
.github/workflows/crdt-sync-precommit.yml
vendored
@ -15,7 +15,7 @@ jobs:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: "3.12"
|
||||
python-version: "3.x"
|
||||
- name: Install dependencies
|
||||
working-directory: crdt-sync
|
||||
run: pip install -r requirements.txt
|
||||
|
||||
8
.github/workflows/crdt-sync-tests.yml
vendored
8
.github/workflows/crdt-sync-tests.yml
vendored
@ -14,17 +14,13 @@ jobs:
|
||||
defaults:
|
||||
run:
|
||||
working-directory: crdt-sync
|
||||
strategy:
|
||||
matrix:
|
||||
python-version: ["3.10", "3.11", "3.12"]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Python ${{ matrix.python-version }}
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
python-version: "3.x"
|
||||
|
||||
- name: Install dependencies
|
||||
run: pip install -r requirements.txt
|
||||
|
||||
2
.github/workflows/file-length.yml
vendored
2
.github/workflows/file-length.yml
vendored
@ -28,7 +28,7 @@ jobs:
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: "3.12"
|
||||
python-version: "3.x"
|
||||
|
||||
- name: Every file is at most 250 lines
|
||||
run: bash scripts/check_file_length.sh --all
|
||||
|
||||
2
.github/workflows/gatelock-precommit.yml
vendored
2
.github/workflows/gatelock-precommit.yml
vendored
@ -17,7 +17,7 @@ jobs:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: "3.12"
|
||||
python-version: "3.x"
|
||||
- name: Install dependencies
|
||||
working-directory: gatelock
|
||||
run: pip install -r requirements.txt
|
||||
|
||||
8
.github/workflows/gatelock-tests.yml
vendored
8
.github/workflows/gatelock-tests.yml
vendored
@ -16,17 +16,13 @@ jobs:
|
||||
defaults:
|
||||
run:
|
||||
working-directory: gatelock
|
||||
strategy:
|
||||
matrix:
|
||||
python-version: ["3.10", "3.11", "3.12"]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Python ${{ matrix.python-version }}
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
python-version: "3.x"
|
||||
|
||||
- name: Install dependencies
|
||||
run: pip install -r requirements.txt
|
||||
|
||||
2
.github/workflows/palette-drift.yml
vendored
2
.github/workflows/palette-drift.yml
vendored
@ -20,7 +20,7 @@ jobs:
|
||||
|
||||
- uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: '3.13'
|
||||
python-version: "3.x"
|
||||
|
||||
# Compares tokens.md (prose freeze), web_ui/src/tokens.css,
|
||||
# design_system tokens.dart and gatelock's LockConfig. Fails on a hex
|
||||
|
||||
2
.github/workflows/shared-gates-tests.yml
vendored
2
.github/workflows/shared-gates-tests.yml
vendored
@ -24,7 +24,7 @@ jobs:
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: "3.13"
|
||||
python-version: "3.x"
|
||||
|
||||
- name: Install test dependencies
|
||||
run: python -m pip install --upgrade pytest pytest-cov pyyaml packaging
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
name = "crdt-sync"
|
||||
version = "0.9.1"
|
||||
description = "Shared CRDT merge scheme + GitHub-Contents-API sync transport for cross-device apps"
|
||||
requires-python = ">=3.10"
|
||||
requires-python = ">=3.14"
|
||||
dependencies = ["requests==2.34.2"]
|
||||
|
||||
# Ship only the library. `tool/` is a package too (it has an __init__.py so
|
||||
|
||||
@ -18,6 +18,10 @@ CARGO: Final = "cargo"
|
||||
GOMOD: Final = "gomod"
|
||||
GITTAG: Final = "gittag"
|
||||
TOOLCHAIN: Final = "toolchain"
|
||||
# The constraint a workflow parser writes for a version MATRIX. Standing
|
||||
# decision (kuhy, 2026-08-28): one toolchain version per repo, always newest,
|
||||
# so a matrix is a finding to delete rather than a range to satisfy.
|
||||
MATRIX: Final = "matrix"
|
||||
|
||||
ECOSYSTEMS: Final = (PUB, PYPI, NPM, CARGO, GOMOD, GITTAG, TOOLCHAIN)
|
||||
|
||||
|
||||
@ -15,7 +15,7 @@ purpose:
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from dep_freshness._tables import TOOLCHAIN
|
||||
from dep_freshness._tables import MATRIX, TOOLCHAIN
|
||||
from dep_freshness.constraints import lower_bound, satisfies
|
||||
from dep_freshness.models import Dep, Finding, Severity
|
||||
from dep_freshness.resolve import Answer
|
||||
@ -27,15 +27,32 @@ def _toolchain(dep: Dep, latest: str) -> Finding | None:
|
||||
if behind(dep.pinned, latest):
|
||||
return Finding(dep, Severity.STALE, latest)
|
||||
return None
|
||||
if dep.constraint == MATRIX:
|
||||
# Standing decision: every repo runs exactly ONE toolchain version and
|
||||
# it is the newest. A matrix is three answers to a one-answer question,
|
||||
# and its older entries are stale by construction.
|
||||
return Finding(
|
||||
dep,
|
||||
Severity.UNPINNED,
|
||||
latest,
|
||||
detail=f"a version MATRIX is not a version: run exactly one "
|
||||
f"{dep.name}, the newest. Replace the whole matrix with a "
|
||||
f'single `{dep.name}-version: "'
|
||||
f'{latest.split(".")[0]}.x"`',
|
||||
)
|
||||
if not dep.constraint or dep.caret_ok is False:
|
||||
return Finding(
|
||||
dep, Severity.UNPINNED, latest,
|
||||
dep,
|
||||
Severity.UNPINNED,
|
||||
latest,
|
||||
detail="a channel name is not a version: two machines on the same "
|
||||
"commit can build with different SDKs",
|
||||
"commit can build with different SDKs",
|
||||
)
|
||||
if not satisfies(dep.constraint, latest):
|
||||
return Finding(
|
||||
dep, Severity.STALE, latest,
|
||||
dep,
|
||||
Severity.STALE,
|
||||
latest,
|
||||
detail=f"the constraint excludes the current toolchain {latest}",
|
||||
)
|
||||
return None
|
||||
@ -45,19 +62,25 @@ def judge(dep: Dep, answer: Answer) -> Finding | None:
|
||||
"""The gate's objection to `dep`, or None when it is fine."""
|
||||
if dep.override:
|
||||
return Finding(
|
||||
dep, Severity.OVERRIDE, answer.version,
|
||||
dep,
|
||||
Severity.OVERRIDE,
|
||||
answer.version,
|
||||
detail="an override pins a transitive dependency nothing else "
|
||||
"watches; allowlist it or remove it",
|
||||
"watches; allowlist it or remove it",
|
||||
)
|
||||
if answer.unavailable:
|
||||
return Finding(
|
||||
dep, Severity.UNKNOWN, None,
|
||||
dep,
|
||||
Severity.UNKNOWN,
|
||||
None,
|
||||
detail="no network and no cached answer",
|
||||
)
|
||||
latest = answer.version
|
||||
if latest is None:
|
||||
return Finding(
|
||||
dep, Severity.UNKNOWN, None,
|
||||
dep,
|
||||
Severity.UNKNOWN,
|
||||
None,
|
||||
detail="the registry has no stable release for this package",
|
||||
)
|
||||
|
||||
@ -70,7 +93,9 @@ def judge(dep: Dep, answer: Answer) -> Finding | None:
|
||||
# The meaningful question is whether the range still admits latest.
|
||||
if not satisfies(dep.constraint, latest):
|
||||
return Finding(
|
||||
dep, Severity.STALE, latest,
|
||||
dep,
|
||||
Severity.STALE,
|
||||
latest,
|
||||
detail=f"the peer range excludes the current {latest}",
|
||||
)
|
||||
return None
|
||||
@ -80,12 +105,16 @@ def judge(dep: Dep, answer: Answer) -> Finding | None:
|
||||
floor = lower_bound(dep.constraint)
|
||||
if floor and behind(floor, latest):
|
||||
return Finding(
|
||||
dep, Severity.STALE, latest,
|
||||
dep,
|
||||
Severity.STALE,
|
||||
latest,
|
||||
detail=f"range floor {floor} is behind latest stable",
|
||||
)
|
||||
return None
|
||||
return Finding(
|
||||
dep, Severity.UNPINNED, latest,
|
||||
dep,
|
||||
Severity.UNPINNED,
|
||||
latest,
|
||||
detail=f"exact-pin it ({latest}) so freshness is checkable",
|
||||
)
|
||||
|
||||
@ -93,7 +122,9 @@ def judge(dep: Dep, answer: Answer) -> Finding | None:
|
||||
return Finding(dep, Severity.STALE, latest)
|
||||
if dep.locked and dep.locked != dep.pinned:
|
||||
return Finding(
|
||||
dep, Severity.LOCK_MISMATCH, latest,
|
||||
dep,
|
||||
Severity.LOCK_MISMATCH,
|
||||
latest,
|
||||
detail=f"lockfile resolved {dep.locked}, manifest pins {dep.pinned}",
|
||||
)
|
||||
return None
|
||||
|
||||
@ -8,12 +8,26 @@ went red on `discarded_futures` for exactly this reason: the runner was on
|
||||
Flutter 3.44.9, which predates the `@awaitNotRequired` annotation that makes
|
||||
the same `unawaited(...)` call a violation on 3.47.2.
|
||||
|
||||
`actions/setup-python` is the same story and cost testsAndMisc a red run on
|
||||
2026-08-28: `python-tests.yml` still said `python-version: "3.11"` while the
|
||||
repo's pins had moved to `numpy 2.5.2`, which requires >=3.12. Nothing local
|
||||
could see it -- the gate judged the interpreter in the shell, not the one the
|
||||
runner installs.
|
||||
|
||||
So the CI pin is a dependency like any other, judged against the same latest
|
||||
stable. Keeping both honest that way needs no separate "these must agree"
|
||||
rule: two values both required to equal latest already agree.
|
||||
|
||||
A `${{ ... }}` expression is skipped -- it resolves at run time, and guessing
|
||||
is worse than saying nothing.
|
||||
**A version MATRIX is a finding, not a support range.** Standing decision
|
||||
(kuhy, 2026-08-28): every repo runs EXACTLY ONE toolchain version and it is
|
||||
always the newest. `python-version: ["3.10", "3.11", "3.12"]` is three answers
|
||||
to a question that has one, and the two oldest are guaranteed to be behind. It
|
||||
is reported so it gets deleted, not bumped.
|
||||
|
||||
Two things are skipped rather than guessed: a `${{ ... }}` expression, which
|
||||
resolves at run time, and a wildcard like `3.x`, which `setup-python` already
|
||||
resolves to the newest stable and therefore cannot drift. `3.x` is what the
|
||||
fleet standardised on, so the common case makes zero registry calls.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
@ -21,18 +35,21 @@ from __future__ import annotations
|
||||
import re
|
||||
from pathlib import Path
|
||||
|
||||
from dep_freshness._tables import TOOLCHAIN
|
||||
from dep_freshness._tables import MATRIX, TOOLCHAIN
|
||||
from dep_freshness.models import Dep
|
||||
from dep_freshness.versions import exact_pin
|
||||
|
||||
WORKFLOW_DIR = ".github/workflows"
|
||||
|
||||
# `flutter-version: 3.47.2`, `node-version: '24.18.0'`, quoted or not.
|
||||
# `flutter-version: 3.47.2`, `node-version: '24.18.0'`, quoted or not, and the
|
||||
# inline-mapping form `with: {python-version: "3.12"}` that build_your_x uses.
|
||||
_PIN = re.compile(
|
||||
r"""^\s*(?P<tool>flutter|node)-version:\s*["']?(?P<value>[^"'#\s]+)["']?"""
|
||||
r"""(?:^|[{,])\s*(?P<tool>flutter|node|python)-version:(?P<rest>.*)$"""
|
||||
)
|
||||
# A channel name is not a version, and `.fvmrc` already reports that case.
|
||||
_NOT_A_VERSION = frozenset({"stable", "beta", "master", "main", "dev", "lts", "latest"})
|
||||
# `3.x`, `24.x`, bare `x`: already "newest", so there is nothing to compare.
|
||||
_WILDCARD = re.compile(r"^\d+(\.\d+)*\.x$|^x$")
|
||||
|
||||
|
||||
def is_workflow(path: Path) -> bool:
|
||||
@ -44,6 +61,33 @@ def is_workflow(path: Path) -> bool:
|
||||
)
|
||||
|
||||
|
||||
def _scalar(rest: str) -> str:
|
||||
"""The bare version out of everything that can follow the colon.
|
||||
|
||||
Handles the quoting, a trailing `}` from an inline mapping, and a trailing
|
||||
`# comment`. Returns "" when the line declares no value at all, which in
|
||||
YAML means a block list follows -- i.e. a matrix written the long way.
|
||||
"""
|
||||
text = rest.split("#", 1)[0].strip().rstrip("}").strip()
|
||||
return text.strip("\"'")
|
||||
|
||||
|
||||
def _dep(tool: str, value: str, path: Path, number: int) -> Dep | None:
|
||||
"""The `Dep` for one `<tool>-version:` line, or None when unjudgeable."""
|
||||
if value.startswith("${{") or value in _NOT_A_VERSION or _WILDCARD.match(value):
|
||||
return None
|
||||
# An inline `[...]` list, or an empty value with a block list beneath it.
|
||||
matrix = value.startswith("[") or value == ""
|
||||
return Dep(
|
||||
ecosystem=TOOLCHAIN,
|
||||
name=tool,
|
||||
constraint=MATRIX if matrix else value,
|
||||
path=path,
|
||||
line=number,
|
||||
pinned=None if matrix else exact_pin(value),
|
||||
)
|
||||
|
||||
|
||||
def parse(path: Path) -> list[Dep]:
|
||||
try:
|
||||
text = path.read_text(encoding="utf-8")
|
||||
@ -51,20 +95,10 @@ def parse(path: Path) -> list[Dep]:
|
||||
return []
|
||||
deps: list[Dep] = []
|
||||
for number, line in enumerate(text.splitlines(), start=1):
|
||||
match = _PIN.match(line)
|
||||
match = _PIN.search(line)
|
||||
if not match:
|
||||
continue
|
||||
value = match.group("value")
|
||||
if value.startswith("${{") or value in _NOT_A_VERSION:
|
||||
continue
|
||||
deps.append(
|
||||
Dep(
|
||||
ecosystem=TOOLCHAIN,
|
||||
name=match.group("tool"),
|
||||
constraint=value,
|
||||
path=path,
|
||||
line=number,
|
||||
pinned=exact_pin(value),
|
||||
)
|
||||
)
|
||||
dep = _dep(match.group("tool"), _scalar(match.group("rest")), path, number)
|
||||
if dep is not None:
|
||||
deps.append(dep)
|
||||
return deps
|
||||
|
||||
@ -101,6 +101,22 @@ def test_a_channel_name_in_fvmrc_is_reported_unpinned():
|
||||
assert finding.severity is Severity.UNPINNED
|
||||
|
||||
|
||||
def test_a_version_matrix_is_reported_with_the_single_pin_to_use():
|
||||
"""One toolchain version per repo, always the newest (kuhy, 2026-08-28).
|
||||
|
||||
A matrix has to be DELETED, not bumped, so the detail must not read like
|
||||
"exact-pin it (3.14.7)" -- that would leave the other entries in place.
|
||||
"""
|
||||
finding = judge(
|
||||
dep(ecosystem="toolchain", name="python", constraint="matrix",
|
||||
pinned=None, path=Path(".github/workflows/ci.yml")),
|
||||
Answer("3.14.7"),
|
||||
)
|
||||
assert finding.severity is Severity.UNPINNED
|
||||
assert "MATRIX is not a version" in finding.detail
|
||||
assert 'python-version: "3.x"' in finding.detail
|
||||
|
||||
|
||||
def test_a_pinned_toolchain_behind_latest_is_stale():
|
||||
finding = judge(
|
||||
dep(ecosystem="toolchain", name="flutter", constraint="3.47.1",
|
||||
|
||||
@ -5,12 +5,13 @@ takes its own `flutter-version:`, and that is what the runner gets. When the
|
||||
two disagree the failure is green-locally / red-in-CI with no diff to explain
|
||||
it -- kuhylog's runner was on Flutter 3.44.9, which predates the
|
||||
`@awaitNotRequired` annotation, so the same code was a violation there and
|
||||
clean here.
|
||||
clean here. `actions/setup-python` did the same to testsAndMisc on 2026-08-28.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from dep_freshness import discover
|
||||
from dep_freshness._tables import MATRIX
|
||||
from dep_freshness.parsers import workflow
|
||||
from dep_freshness.tests.conftest import write
|
||||
|
||||
@ -38,10 +39,16 @@ def _path(repo):
|
||||
return repo / ".github/workflows/ci.yml"
|
||||
|
||||
|
||||
def _parse(repo, body):
|
||||
return workflow.parse(write(repo, ".github/workflows/ci.yml", body))
|
||||
|
||||
|
||||
def _names(repo, body):
|
||||
return [d.name for d in _parse(repo, body)]
|
||||
|
||||
|
||||
def test_a_workflow_pin_is_a_toolchain_dependency(repo):
|
||||
deps = {
|
||||
d.name: d for d in workflow.parse(write(repo, ".github/workflows/ci.yml", CI))
|
||||
}
|
||||
deps = {d.name: d for d in _parse(repo, CI)}
|
||||
assert deps["flutter"].pinned == "3.44.9"
|
||||
assert deps["flutter"].line == 12
|
||||
assert deps["node"].pinned == "24.18.0"
|
||||
@ -50,18 +57,60 @@ def test_a_workflow_pin_is_a_toolchain_dependency(repo):
|
||||
|
||||
def test_a_channel_name_is_not_a_version(repo):
|
||||
body = CI.replace("flutter-version: 3.44.9", "flutter-version: stable")
|
||||
names = [
|
||||
d.name for d in workflow.parse(write(repo, ".github/workflows/ci.yml", body))
|
||||
]
|
||||
assert "flutter" not in names, "a channel tells the gate nothing to compare"
|
||||
assert "flutter" not in _names(repo, body), "a channel gives nothing to compare"
|
||||
|
||||
|
||||
def test_a_runtime_expression_is_skipped_rather_than_guessed(repo):
|
||||
body = CI.replace("flutter-version: 3.44.9", "flutter-version: ${{ env.FLUTTER }}")
|
||||
names = [
|
||||
d.name for d in workflow.parse(write(repo, ".github/workflows/ci.yml", body))
|
||||
]
|
||||
assert "flutter" not in names
|
||||
assert "flutter" not in _names(repo, body)
|
||||
|
||||
|
||||
def test_a_setup_python_pin_is_a_toolchain_dependency(repo):
|
||||
"""testsAndMisc went red because its runner was still on 3.11.
|
||||
|
||||
Its pins had moved to numpy 2.5.2, which declares >=3.12. Nothing local
|
||||
could see it: the gate judged the interpreter in the shell, never the one
|
||||
`actions/setup-python` installs.
|
||||
"""
|
||||
body = CI + ' python-version: "3.11"\n'
|
||||
deps = {d.name: d for d in _parse(repo, body)}
|
||||
assert deps["python"].pinned == "3.11"
|
||||
assert deps["python"].ecosystem == "toolchain"
|
||||
|
||||
|
||||
def test_a_wildcard_has_nothing_to_compare(repo):
|
||||
"""`3.x` already resolves to newest stable, so it cannot drift behind."""
|
||||
for value in ('"3.x"', '"24.x"', '"x"'):
|
||||
body = CI + f" python-version: {value}\n"
|
||||
assert "python" not in _names(repo, body), value
|
||||
|
||||
|
||||
def test_an_inline_matrix_is_reported_rather_than_bumped(repo):
|
||||
"""One version per repo, always newest: a matrix is deleted, not raised."""
|
||||
body = CI + ' python-version: ["3.10", "3.11", "3.12"]\n'
|
||||
deps = {d.name: d for d in _parse(repo, body)}
|
||||
assert deps["python"].constraint == MATRIX
|
||||
assert deps["python"].pinned is None
|
||||
|
||||
|
||||
def test_a_block_list_matrix_is_caught_too(repo):
|
||||
"""The long form declares no value on the line; the list is beneath it."""
|
||||
body = CI + " python-version:\n - '3.10'\n - '3.11'\n"
|
||||
deps = {d.name: d for d in _parse(repo, body)}
|
||||
assert deps["python"].constraint == MATRIX
|
||||
|
||||
|
||||
def test_an_inline_mapping_pin_is_found(repo):
|
||||
"""build_your_x writes `with: {python-version: "3.12"}` on one line."""
|
||||
body = CI + ' with: {python-version: "3.12"}\n'
|
||||
deps = {d.name: d for d in _parse(repo, body)}
|
||||
assert deps["python"].pinned == "3.12"
|
||||
|
||||
|
||||
def test_a_trailing_comment_is_not_part_of_the_version(repo):
|
||||
body = CI + ' python-version: "3.12" # oldest supported\n'
|
||||
deps = {d.name: d for d in _parse(repo, body)}
|
||||
assert deps["python"].pinned == "3.12"
|
||||
|
||||
|
||||
def test_an_unreadable_workflow_yields_nothing(repo):
|
||||
|
||||
@ -6,7 +6,7 @@ build-backend = "setuptools.build_meta"
|
||||
name = "gatelock"
|
||||
version = "0.7.2"
|
||||
description = "Shared per-output lock-window + HMAC log-integrity backend for blocking-overlay apps"
|
||||
requires-python = ">=3.10"
|
||||
requires-python = ">=3.14"
|
||||
dependencies = [] # pure stdlib — tkinter is bundled with Python
|
||||
|
||||
[project.optional-dependencies]
|
||||
|
||||
@ -35,7 +35,7 @@
|
||||
"devDependencies": {
|
||||
"@eslint/js": "10.0.1",
|
||||
"@testing-library/jest-dom": "7.0.1",
|
||||
"@testing-library/react": "16.3.2",
|
||||
"@testing-library/react": "16.3.3",
|
||||
"@testing-library/user-event": "14.6.6",
|
||||
"@types/react": "19.2.18",
|
||||
"@types/react-dom": "19.2.5",
|
||||
|
||||
@ -15,8 +15,8 @@ importers:
|
||||
specifier: 7.0.1
|
||||
version: 7.0.1(@testing-library/dom@10.4.1)(vitest@4.1.11)
|
||||
'@testing-library/react':
|
||||
specifier: 16.3.2
|
||||
version: 16.3.2(@testing-library/dom@10.4.1)(@types/react-dom@19.2.5(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
|
||||
specifier: 16.3.3
|
||||
version: 16.3.3(@testing-library/dom@10.4.1)(@types/react-dom@19.2.5(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
|
||||
'@testing-library/user-event':
|
||||
specifier: 14.6.6
|
||||
version: 14.6.6(@testing-library/dom@10.4.1)
|
||||
@ -333,8 +333,8 @@ packages:
|
||||
vitest:
|
||||
optional: true
|
||||
|
||||
'@testing-library/react@16.3.2':
|
||||
resolution: {integrity: sha512-XU5/SytQM+ykqMnAnvB2umaJNIOsLF3PVv//1Ew4CTcpz0/BRyy/af40qqrt7SjKpDdT1saBMc42CUok5gaw+g==}
|
||||
'@testing-library/react@16.3.3':
|
||||
resolution: {integrity: sha512-Uo193NgQbPMz6lrrhtRQQFcMC6Re/ELLFbbuVL30WDlZxlpZf9/lMHTAVxPRLw1q1iu9OJmR1c2BLiENRstdBg==}
|
||||
engines: {node: '>=18'}
|
||||
peerDependencies:
|
||||
'@testing-library/dom': ^10.0.0
|
||||
@ -1402,7 +1402,7 @@ snapshots:
|
||||
optionalDependencies:
|
||||
vitest: 4.1.11(@types/node@26.4.0)(@vitest/coverage-v8@4.1.11)(jsdom@30.0.1)(vite@8.2.2(@types/node@26.4.0))
|
||||
|
||||
'@testing-library/react@16.3.2(@testing-library/dom@10.4.1)(@types/react-dom@19.2.5(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)':
|
||||
'@testing-library/react@16.3.3(@testing-library/dom@10.4.1)(@types/react-dom@19.2.5(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)':
|
||||
dependencies:
|
||||
'@babel/runtime': 7.29.7
|
||||
'@testing-library/dom': 10.4.1
|
||||
|
||||
Loading…
Reference in New Issue
Block a user