mirror of
https://github.com/kuhyx/utils.git
synced 2026-11-11 16:34:02 +01:00
Some checks are pending
dependency freshness / dependencies on newest stable (push) Waiting to run
file length / file-length (push) Waiting to run
md-naming / markdown naming convention (push) Waiting to run
palette drift / palette (push) Waiting to run
shared gate tests / gate unit tests (push) Waiting to run
`.fvmrc` is a declaration nothing in this fleet reads. `subosito/flutter-action`
takes its own `flutter-version:`, and that pin is what the runner installs, so
the two drift in silence and the failure mode is the worst kind: green
locally, red in CI, with no diff to explain it.
kuhylog is the live case. Its runner is on Flutter **3.44.9**, which predates
the `@awaitNotRequired` annotation, so `unawaited(Navigator.push(...))` is
`unnecessary_unawaited` on 3.47.2 here and `discarded_futures` there -- the two
SDKs demand opposite code and CI went red on a commit that is clean locally.
The fleet is spread across 3.44.6, 3.44.8, 3.47.0 and 3.47.1 with no one
tracking it.
So a workflow's `flutter-version:` / `node-version:` is now a dependency like
any other, judged against the same latest stable. That needs no separate
"these must agree" rule: two values both required to equal latest already
agree. A channel name and a `${{ }}` expression are skipped -- one is not a
version, the other resolves at run time and guessing is worse than silence.
Found real drift on its first run: both TypeScript workflows here pinned
`node-version: 24`, a floating major that lets two runs of the same commit
install different Node. Now 24.20.0.
321 tests, 100% branch coverage on every production module.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CRykabMwPgDzFHcARoqpaK
71 lines
2.4 KiB
Python
71 lines
2.4 KiB
Python
"""`.github/workflows/*.yml` -> the toolchain versions CI actually builds with.
|
|
|
|
`.fvmrc` declares the SDK, but nothing in this fleet reads it: `subosito/
|
|
flutter-action` takes its own `flutter-version:`, and that pin is what the
|
|
runner really installs. The two drift silently, and the failure mode is the
|
|
worst kind -- green locally, red in CI, with no diff to explain it. kuhylog
|
|
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.
|
|
|
|
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.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import re
|
|
from pathlib import Path
|
|
|
|
from dep_freshness._tables import 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.
|
|
_PIN = re.compile(
|
|
r"""^\s*(?P<tool>flutter|node)-version:\s*["']?(?P<value>[^"'#\s]+)["']?"""
|
|
)
|
|
# A channel name is not a version, and `.fvmrc` already reports that case.
|
|
_NOT_A_VERSION = frozenset({"stable", "beta", "master", "main", "dev", "lts", "latest"})
|
|
|
|
|
|
def is_workflow(path: Path) -> bool:
|
|
"""True for a file inside a repo's `.github/workflows` directory."""
|
|
return (
|
|
path.suffix in (".yml", ".yaml")
|
|
and path.parent.name == "workflows"
|
|
and path.parent.parent.name == ".github"
|
|
)
|
|
|
|
|
|
def parse(path: Path) -> list[Dep]:
|
|
try:
|
|
text = path.read_text(encoding="utf-8")
|
|
except OSError:
|
|
return []
|
|
deps: list[Dep] = []
|
|
for number, line in enumerate(text.splitlines(), start=1):
|
|
match = _PIN.match(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),
|
|
)
|
|
)
|
|
return deps
|