"""Static data for the dependency-freshness gate. Every tunable lives here so `config.py` stays a re-export surface and the ecosystem modules stay pure logic. Changing a registry URL, a TTL or the Node release channel is a one-line edit in this file and nowhere else. """ from __future__ import annotations from typing import Final # --- Ecosystems ------------------------------------------------------------- PUB: Final = "pub" PYPI: Final = "pypi" NPM: Final = "npm" CARGO: Final = "cargo" GOMOD: Final = "gomod" GITTAG: Final = "gittag" MAVEN: Final = "maven" # A JitPack artifact pinned to a commit hash instead of a tag. "Newest" is # the source repository's default-branch HEAD: a hash pin has no release to # wait for, so the only reading of "current" it admits is "at HEAD". GITCOMMIT: Final = "gitcommit" 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, MAVEN, GITCOMMIT, TOOLCHAIN, ) # --- Registry endpoints ----------------------------------------------------- PUB_API: Final = "https://pub.dev/api/packages/{name}" PYPI_API: Final = "https://pypi.org/simple/{name}/" NPM_API: Final = "https://registry.npmjs.org/{name}" CRATES_API: Final = "https://crates.io/api/v1/crates/{name}" GOPROXY_API: Final = "https://proxy.golang.org/{name}/@latest" FLUTTER_RELEASES: Final = ( "https://storage.googleapis.com/flutter_infra_release/releases/releases_linux.json" ) NODE_RELEASES: Final = "https://nodejs.org/dist/index.json" GRADLE_RELEASES: Final = "https://services.gradle.org/versions/current" # Asked in order; a 404 moves on. Google first because AndroidX and the # Android Gradle plugin exist nowhere else, and Central answers 404 fast. # JitPack is not on the list: since 2026-09 its maven-metadata answers # HTTP 401 "No access token" (artifact downloads still work), so `com.github.*` # artifacts published nowhere else (PhotoView, DirectionalViewPager, ...) # are answered from the source repo's tags instead -- which is all JitPack # ever built. MAVEN_REPOS: Final = ( "https://dl.google.com/dl/android/maven2/", "https://repo1.maven.org/maven2/", "https://plugins.gradle.org/m2/", ) UTILS_TAG_REMOTE: Final = "https://github.com/kuhyx/utils" # JitPack coordinates: `com.github.:` or # `com.github..:`; the GitHub repo is what "HEAD" means. JITPACK_GROUP_PREFIX: Final = "com.github." GITHUB_REMOTE: Final = "https://github.com/{repo}" # pnpm 11 quarantines packages younger than this before it will install them, # to blunt the window in which a compromised publish is live. The gate matches # it: reporting a repo as behind a version its package manager refuses to # install is a failure nobody can act on. Its default is 1440 minutes. NPM_QUARANTINE_HOURS: Final = 24 # crates.io 403s without one; npm/PyPI want a contactable agent too. USER_AGENT: Final = "kuhyx-dependency-freshness (+https://github.com/kuhyx/utils)" # PyPI's modern index; the legacy JSON `info.version` leaks pre-releases. PYPI_ACCEPT: Final = "application/vnd.pypi.simple.v1+json" NPM_ACCEPT: Final = "application/vnd.npm.install-v1+json" # --- Cache ------------------------------------------------------------------ CACHE_PATH_ENV: Final = "DEP_FRESHNESS_CACHE" DEFAULT_CACHE_DIR: Final = "~/.cache/dep-freshness" CACHE_FILE: Final = "registry.json" TTL_SECONDS: Final = 6 * 3600 TTL_GITTAG_SECONDS: Final = 24 * 3600 # also gitcommit: both cost a subprocess HTTP_TIMEOUT: Final = 10.0 # JitPack generates maven-metadata on request and routinely takes longer than # the first timeout; one such read failed a whole CI gates run on 2026-09-12. # Each retry waits `attempt * HTTP_TIMEOUT`, so three attempts allow a minute. HTTP_ATTEMPTS: Final = 3 PROBE_TIMEOUT: Final = 2.0 MAX_WORKERS: Final = 8 # --- Toolchain targets ------------------------------------------------------ # Q2 read literally points at Node Current; this machine and every repo build # against LTS, so the gate targets LTS. One-line switch to "current". NODE_CHANNEL: Final = "lts" # Python is a registry answer like every other toolchain (kuhy, 2026-10-20). # It used to be the interpreter running the gate, which made the verdict # depend on the machine: `setup-python` "3.x" gave spellcode 3.15.0 on one # runner and 3.14.8 on the next. See registries/toolchain.py for the source. PYTHON_RELEASES: Final = ( "https://www.python.org/api/v2/downloads/release/" "?is_published=true&pre_release=false" ) # --- Manifest discovery ----------------------------------------------------- MANIFEST_GLOBS: Final = ( "pubspec.yaml", "pyproject.toml", "package.json", "Cargo.toml", "go.mod", ".fvmrc", ".nvmrc", ".python-version", "gradle-wrapper.properties", ) REQUIREMENTS_PATTERN: Final = r"^requirements.*\.txt$" # `.utils` is where every CI workflow checks the shared gate out, INSIDE the # repo being checked. Without it `--all` walks utils' own manifests and fails # the consuming repo for staleness in a directory that repo does not own -- # which is exactly how punchme's first green local run turned red in CI. EXCLUDED_DIRS: Final = frozenset( { ".git", "node_modules", "build", "dist", ".dart_tool", ".venv", "venv", "__pycache__", "target", "vendor", ".gradle", "ios", "macos", "windows", ".mypy_cache", ".pytest_cache", ".ruff_cache", "coverage", "htmlcov", ".idea", ".vscode", "Pods", ".fvm", ".utils", } ) # --- Constraint policy ------------------------------------------------------ # Q13: these resolve from the Flutter/Dart SDK or are lint packages coupled to # it, so a caret range is allowed. An exact pin is still checked for staleness. PUB_CARET_ALLOWED: Final = frozenset( { "flutter", "flutter_test", "flutter_localizations", "flutter_driver", "flutter_web_plugins", "integration_test", "sky_engine", "flutter_lints", "very_good_analysis", "lints", } ) # Packages that ship with the Flutter SDK: no registry version exists. PUB_SDK_PACKAGES: Final = frozenset( { "flutter", "flutter_test", "flutter_localizations", "flutter_driver", "flutter_web_plugins", "integration_test", "sky_engine", } ) # `dependency_overrides` is an unpinned dependency wearing a disguise. PUB_OVERRIDE_KEY: Final = "dependency_overrides" ALLOWLIST_FILE: Final = "dependency-freshness.allowlist.yaml" # A blocker like "typescript-eslint does not support TS 7" holds in EVERY repo # that pins typescript, so a repo-only allowlist would mean hand-copying one # entry into eight files that then all rot independently -- the four-way fork # of the 250-line cap, in the mechanism built to avoid it. The shared file in # ~/src/utils is inherited; a repo entry for the same package overrides it. SHARED_ALLOWLIST_ENV: Final = "DEP_FRESHNESS_SHARED_ALLOWLIST" ALLOWLIST_MAX_DAYS: Final = 90 TRANSITIVE_PREFIX: Final = "transitive:" # A fork's predicate: the entry holds only while the upstream repository's own # copy of the same manifest pins the same version, and dies the day upstream # bumps. A fork that cannot take a release (e.g. a 300-file accessor rewrite # that breaks every future upstream string) before its upstream does. UPSTREAM_PREFIX: Final = "upstream:" GITHUB_RAW: Final = "https://raw.githubusercontent.com/{repo}/HEAD/{path}"