diff --git a/.github/scripts/cargo-release-quarantine.py b/.github/scripts/cargo-release-quarantine.py old mode 100644 new mode 100755 index 267fbce..bc485e8 --- a/.github/scripts/cargo-release-quarantine.py +++ b/.github/scripts/cargo-release-quarantine.py @@ -12,46 +12,70 @@ import urllib.parse import urllib.request from pathlib import Path +from typing import NotRequired, TypedDict, cast CRATES_IO_MARKERS = ("crates.io-index", "index.crates.io") -_cache: dict[str, dict[str, dict]] = {} + + +class LockPackage(TypedDict): + name: str + version: str + source: NotRequired[str] + + +class VersionInfo(TypedDict): + num: str + created_at: NotRequired[str] + + +class CratesResponse(TypedDict): + versions: list[VersionInfo] + + +_cache: dict[str, dict[str, VersionInfo]] = {} def git_show(ref: str, path: str) -> bytes: return subprocess.check_output(["git", "show", f"{ref}:{path}"]) -def load_lock(raw: bytes) -> list[dict]: - return tomllib.loads(raw.decode()).get("package", []) +def load_lock(raw: bytes) -> list[LockPackage]: + packages = tomllib.loads(raw.decode()).get("package", []) + return cast(list[LockPackage], packages) -def key(pkg: dict) -> tuple[str, str, str | None]: +def key(pkg: LockPackage) -> tuple[str, str, str | None]: return pkg["name"], pkg["version"], pkg.get("source") -def is_crates_io(pkg: dict) -> bool: +def is_crates_io(pkg: LockPackage) -> bool: source = pkg.get("source") or "" - return source.startswith("registry+") and any(marker in source for marker in CRATES_IO_MARKERS) + return source.startswith("registry+") and any( + marker in source for marker in CRATES_IO_MARKERS + ) -def crate_versions(name: str) -> dict[str, dict]: +def crate_versions(name: str) -> dict[str, VersionInfo]: if name in _cache: return _cache[name] req = urllib.request.Request( "https://crates.io/api/v1/crates/" + urllib.parse.quote(name, safe=""), - headers={"Accept": "application/json", "User-Agent": "codeandsolder-renovate-quarantine/1.0"}, + headers={ + "Accept": "application/json", + "User-Agent": "codeandsolder-renovate-quarantine/1.0", + }, ) with urllib.request.urlopen(req, timeout=30) as response: - payload = json.load(response) + payload = cast(CratesResponse, json.load(response)) versions = {item["num"]: item for item in payload.get("versions", [])} _cache[name] = versions return versions -def publication_time(pkg: dict) -> dt.datetime | None: +def publication_time(pkg: LockPackage) -> dt.datetime | None: info = crate_versions(pkg["name"]).get(pkg["version"]) value = info.get("created_at") if info else None - return dt.datetime.fromisoformat(value.replace("Z", "+00:00")) if value else None + return dt.datetime.fromisoformat(value) if value else None def main() -> int: @@ -67,13 +91,15 @@ def main() -> int: after = load_lock(Path("Cargo.lock").read_bytes()) changed = [pkg for pkg in after if key(pkg) not in before and is_crates_io(pkg)] - cutoff = dt.datetime.now(dt.timezone.utc) - dt.timedelta(hours=args.hours) + cutoff = dt.datetime.now(dt.UTC) - dt.timedelta(hours=args.hours) blocked: list[str] = [] for pkg in changed: when = publication_time(pkg) if when is None: - blocked.append(f"{pkg['name']} {pkg['version']}: missing authoritative crates.io publication time") + blocked.append( + f"{pkg['name']} {pkg['version']}: missing authoritative crates.io publication time" + ) elif when > cutoff: blocked.append( f"{pkg['name']} {pkg['version']}: published {when.isoformat()}, " @@ -86,7 +112,9 @@ def main() -> int: print(f"- {item}", file=sys.stderr) return 1 - print(f"Cargo release-age quarantine passed for {len(changed)} newly introduced crates.io package versions.") + print( + f"Cargo release-age quarantine passed for {len(changed)} newly introduced crates.io package versions." + ) return 0 diff --git a/.github/scripts/render-maintenance-template.py b/.github/scripts/render-maintenance-template.py old mode 100644 new mode 100755 diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f6ab54a..7b91346 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -26,7 +26,7 @@ env: jobs: rust-skills2-strict: name: rust-skills2 strict nightly gate - uses: codeandsolder/rust-skills2/.github/workflows/strict-rust.yml@ca8e5368b41e5699095dd0458a3849ecb8db7712 + uses: codeandsolder/rust-skills2/.github/workflows/strict-rust.yml@cc82bd6c31d8f9e5e6e2a3cc9d2f00663bd57e15 # main lockfile: name: Lockfile consistency @@ -223,7 +223,7 @@ jobs: if: github.event_name != 'pull_request' permissions: contents: read - uses: codeandsolder/ci-workflows/.github/workflows/rust-sccache.yml@main + uses: codeandsolder/ci-workflows/.github/workflows/rust-sccache.yml@b2414c5358d1061e9b442656298506e98c9d13e7 # main with: command: cargo +1.99.0 test --locked --workspace --lib rust-toolchain: "1.99.0"