mirror of
https://github.com/Abdess/retroarch_system.git
synced 2026-11-06 01:53:56 -06:00
feat: compare the site npm pins with the registry
The ci area of check_freshness read the PyPI and action pins only. The exact versions website/package.json pins (astro, starlight) now get a row each, STALE once npm publishes a newer release.
This commit is contained in:
1 parent
33db4e9c83
commit
ab1866fd66
3 files changed
+93
-3
No files matched your search
@@ -79,6 +79,9 @@ TOSEC_DOWNLOADS = "https://www.tosecdev.org/downloads"
|
||||
MAME_REPO = "https://github.com/mamedev/mame"
|
||||
FBNEO_REPO = "https://github.com/libretro/FBNeo"
|
||||
PYPI_URL = "https://pypi.org/pypi/{name}/json"
|
||||
NPM_URL = "https://registry.npmjs.org/{name}/latest"
|
||||
# The site's npm dependencies, pinned to exact versions and built by Deploy Site.
|
||||
SITE_PACKAGE = REPO_ROOT / "website" / "package.json"
|
||||
INSTALLERS = ("install.sh", "install.ps1")
|
||||
|
||||
SHOWN_ITEMS = 4 # items named in a diff summary before ", ..."
|
||||
@@ -1355,6 +1358,53 @@ def pypi_latest(name: str) -> str | None:
|
||||
return str(info.get("version") or "") or None
|
||||
|
||||
|
||||
def parse_npm_pins(text: str) -> dict[str, str]:
|
||||
"""Package -> the version a package.json names, dependencies of both kinds."""
|
||||
data = json.loads(text)
|
||||
pins: dict[str, str] = {}
|
||||
for key in ("dependencies", "devDependencies"):
|
||||
pins.update({str(k): str(v) for k, v in (data.get(key) or {}).items()})
|
||||
return pins
|
||||
|
||||
|
||||
def npm_latest(name: str) -> str | None:
|
||||
payload = upstream._http_json(NPM_URL.format(name=name)) # noqa: SLF001
|
||||
if not isinstance(payload, dict):
|
||||
return None
|
||||
return str(payload.get("version") or "") or None
|
||||
|
||||
|
||||
def _npm_findings() -> list[Finding]:
|
||||
"""One row per site dependency: an exact pin is stale once npm's latest
|
||||
release is newer, since nothing else asks npm."""
|
||||
findings = []
|
||||
for name, pinned in sorted(parse_npm_pins(SITE_PACKAGE.read_text(encoding="utf-8")).items()):
|
||||
subject = f"npm:{name}"
|
||||
if not re.fullmatch(r"\d+(\.\d+)*", pinned):
|
||||
findings.append(Finding("ci", subject, UNKNOWN, local=pinned, detail="a range, not an exact pin"))
|
||||
continue
|
||||
try:
|
||||
latest = npm_latest(name)
|
||||
except upstream.UpstreamError as exc:
|
||||
findings.append(Finding("ci", subject, UNKNOWN, local=pinned, detail=str(exc)))
|
||||
continue
|
||||
if latest is None:
|
||||
findings.append(Finding("ci", subject, UNKNOWN, local=pinned, detail="not on npm"))
|
||||
continue
|
||||
newer = _version_tuple(latest) > _version_tuple(pinned)
|
||||
findings.append(
|
||||
Finding(
|
||||
"ci",
|
||||
subject,
|
||||
STALE if newer else OK,
|
||||
local=pinned,
|
||||
remote=latest,
|
||||
detail="a newer release is out" if newer else "",
|
||||
)
|
||||
)
|
||||
return findings
|
||||
|
||||
|
||||
def check_ci(cache_dir: str, offline: bool) -> list[Finding]:
|
||||
findings = [_check_installer_pins()]
|
||||
workflows = sorted((REPO_ROOT / ".github" / "workflows").glob("*.yml"))
|
||||
@@ -1366,8 +1416,9 @@ def check_ci(cache_dir: str, offline: bool) -> list[Finding]:
|
||||
pins.setdefault(name, spec)
|
||||
actions.update(parse_action_pins(text))
|
||||
if offline:
|
||||
findings.append(Finding("ci", "workflow pins", SKIPPED, detail="offline"))
|
||||
findings.append(Finding("ci", "workflow and site pins", SKIPPED, detail="offline"))
|
||||
return findings
|
||||
findings.extend(_npm_findings())
|
||||
for name, spec in sorted(pins.items()):
|
||||
try:
|
||||
latest = pypi_latest(name)
|
||||
|
||||
@@ -8,6 +8,7 @@ report is folded, since each of those decides whether a row says STALE.
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
import tempfile
|
||||
@@ -353,6 +354,43 @@ class PinParsingTests(unittest.TestCase):
|
||||
self.assertTrue(cf.specifier_allows(">=10.14", "12.0.1"))
|
||||
|
||||
|
||||
class SiteNpmPins(unittest.TestCase):
|
||||
"""The site's npm dependencies are pinned to exact versions in
|
||||
website/package.json, and the ci area compared only the PyPI and action
|
||||
pins: astro 7.4.2 stayed behind 7.4.7 with no STALE row."""
|
||||
|
||||
def test_both_dependency_kinds_are_read(self):
|
||||
pins = cf.parse_npm_pins(
|
||||
'{"dependencies": {"astro": "7.4.2"}, "devDependencies": {"x": "^1.0.0"}}'
|
||||
)
|
||||
self.assertEqual(pins, {"astro": "7.4.2", "x": "^1.0.0"})
|
||||
|
||||
def test_a_newer_release_is_stale(self):
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
package = Path(tmp) / "package.json"
|
||||
package.write_text(json.dumps({"dependencies": {
|
||||
"astro": "7.4.2", "@astrojs/starlight": "0.42.9", "y": "^2.0.0",
|
||||
}}))
|
||||
latest = {"astro": "7.4.7", "@astrojs/starlight": "0.42.9"}
|
||||
def answer(url):
|
||||
name = url.split("registry.npmjs.org/", 1)[1].rsplit("/latest", 1)[0]
|
||||
return {"version": latest[name]}
|
||||
with mock.patch.object(cf, "SITE_PACKAGE", package), \
|
||||
mock.patch.object(cf.upstream, "_http_json", side_effect=answer):
|
||||
rows = {f.subject: f for f in cf._npm_findings()}
|
||||
self.assertEqual(rows["npm:astro"].status, cf.STALE)
|
||||
self.assertEqual(rows["npm:astro"].remote, "7.4.7")
|
||||
self.assertEqual(rows["npm:@astrojs/starlight"].status, cf.OK)
|
||||
self.assertEqual(rows["npm:y"].status, cf.UNKNOWN)
|
||||
|
||||
def test_the_ci_area_reads_the_site_pins(self):
|
||||
with mock.patch.object(cf, "_npm_findings", return_value=[]) as npm, \
|
||||
mock.patch.object(cf, "parse_pip_pins", return_value={}), \
|
||||
mock.patch.object(cf, "parse_action_pins", return_value={}):
|
||||
cf.check_ci("unused", offline=False)
|
||||
npm.assert_called_once()
|
||||
|
||||
|
||||
class RomFolderPins(unittest.TestCase):
|
||||
"""The rom folders of a setup were pinned by hand and nothing compared
|
||||
the pins with the platform file or the project's releases."""
|
||||
|
||||
+3
-2
@@ -688,8 +688,9 @@ directories are checked against their upstream, dump catalogs and romset
|
||||
snapshots against the catalog they were read from (Redump DAT versions, the
|
||||
No-Intro daily rebuild, the newest TOSEC pack, the newest MAME release, the
|
||||
git blobs of the FBNeo DATs), the game catalogs of `games/catalogs/` against
|
||||
the No-Intro pack and the Redump datfiles they were read from, and the CI
|
||||
toolchain against PyPI and the actions' latest releases.
|
||||
the No-Intro pack and the Redump datfiles they were read from, the CI
|
||||
toolchain against PyPI and the actions' latest releases, and the site's exact
|
||||
npm pins in `website/package.json` against the npm registry.
|
||||
|
||||
The `native` area needs no network. It reads the cache `export_native.py`
|
||||
patches from and reports an original that is absent, that came from another
|
||||
|
||||
Reference in new issue
Block a user