diff --git a/scripts/check_freshness.py b/scripts/check_freshness.py index ca555380..604fcfcb 100644 --- a/scripts/check_freshness.py +++ b/scripts/check_freshness.py @@ -1253,8 +1253,6 @@ def check_profiles(run: bool, offline: bool) -> list[Finding]: "--all", "--json", "--check-version", - "--detect-new-files", - "--watch-hashes", ] if offline: cmd.append("--offline") diff --git a/scripts/profile_sync.py b/scripts/profile_sync.py index 1b890213..2d670119 100644 --- a/scripts/profile_sync.py +++ b/scripts/profile_sync.py @@ -950,6 +950,10 @@ class ProfileReport: # review, or the triage read "0 to review" on a profile whose pin could # not move (blastem, xenia-edge, lindbergh-loader). stale_prose: list[str] | None = None + # --check-version on a forge: core_version against the latest tag and + # release, kept on the report so every output carries it. + version: VersionReport | None = None + version_warning: str | None = None # Skips that say the profile cannot be checked as written: a repository # that no longer answers, no pin to read, a death the forge contradicts. @@ -1958,6 +1962,15 @@ def format_report(report: ProfileReport, changed_only: bool = False) -> str: if report.page_version and report.page_version == report.no_genuine_copy else "") ) + if report.version: + version = report.version + lines.append( + f" version: declared {version.declared}, " + f"latest tag {version.latest_tag}, " + f"latest release {version.latest_release} ({version.release_date})" + ) + if report.version_warning: + lines.append(f" {report.version_warning}") if report.skipped: lines.append(f" skipped: {report.skipped}") return "\n".join(lines) @@ -3632,23 +3645,6 @@ def _cited_paths(report: ProfileReport) -> list[str]: ) -def _print_version( - args: argparse.Namespace, profile: dict, report: ProfileReport, - repo: upstream.Repo, -) -> None: - version = check_version(profile, repo, args.cache_dir, args.offline) - if version is None: - return - print( - f" version: declared {version.declared}, " - f"latest tag {version.latest_tag}, " - f"latest release {version.latest_release} ({version.release_date})" - ) - warning = version_warning(version, report.pin) - if warning: - print(f" {warning}") - - def _print_detection( args: argparse.Namespace, profile: dict, report: ProfileReport, repo: upstream.Repo, @@ -3682,6 +3678,27 @@ def _print_detection( print(f" new {kind}: {path} {value}") +def _attach_version( + args: argparse.Namespace, profile: dict, report: ProfileReport +) -> None: + """Record core_version against the forge's latest tag and release. + + Kept on the report rather than printed, so --json carries it as the text + report does. A forge that refuses is named on the profile's own line. + """ + repo = select_repo(profile) if report.head else None + if repo is None: + return + try: + report.version = check_version(profile, repo, args.cache_dir, args.offline) + except upstream.RateLimitError: + raise + except upstream.UpstreamError as exc: + print(f"{report.name}: version check skipped: {exc}", file=sys.stderr) + return + report.version_warning = version_warning(report.version, report.pin) + + def _print_extras( args: argparse.Namespace, profile: dict, report: ProfileReport ) -> None: @@ -3693,8 +3710,7 @@ def _print_extras( stops everything. """ wants = ( - args.check_version - or args.detect_new_files + args.detect_new_files or args.watch_hashes or args.full_diff or args.tree_diff @@ -3716,8 +3732,6 @@ def _print_extras_from( args: argparse.Namespace, profile: dict, report: ProfileReport, repo: upstream.Repo, ) -> None: - if args.check_version: - _print_version(args, profile, report, repo) if args.detect_new_files or args.watch_hashes: _print_detection(args, profile, report, repo) if args.full_diff: @@ -3864,9 +3878,13 @@ def _refuse_flag_combinations(args: argparse.Namespace) -> None: ("--triage", args.triage), ) if on ] + # The version check lives on the report, so the outputs built from the + # report (text, --json, --markdown) carry it. A URL list and a ranking + # do not. + lists_only = args.fetch_plan or args.triage extras = [ flag for flag, on in ( - ("--check-version", args.check_version), + ("--check-version", args.check_version and lists_only), ("--detect-new-files", args.detect_new_files), ("--watch-hashes", args.watch_hashes), ("--full-diff", args.full_diff), @@ -3963,6 +3981,8 @@ def main() -> None: report.page_version = page_version(profile, args.cache_dir, args.offline) report.no_genuine_copy = str( profile["version_source"].get("no_genuine_copy") or "") or None + if args.check_version: + _attach_version(args, profile, report) reports.append(report) if writes: try: diff --git a/tests/test_check_freshness.py b/tests/test_check_freshness.py index a0c36df1..664fc636 100644 --- a/tests/test_check_freshness.py +++ b/tests/test_check_freshness.py @@ -388,6 +388,24 @@ class ProfileSummaryTests(unittest.TestCase): self.assertNotIn("a", by_subject) self.assertNotIn("b", by_subject) + def test_profile_sync_accepts_the_command_it_is_given(self): + """--json with the detection flags was refused, so --profiles read an + empty stdout and reported "unreadable report" on every run.""" + import profile_sync # noqa: PLC0415 + + captured = [] + + def run(cmd, **kwargs): + captured.append(cmd) + return mock.Mock(returncode=0, stdout="[]", stderr="") + + with mock.patch.object(cf.subprocess, "run", side_effect=run): + cf.check_profiles(True, offline=True) + (cmd,) = captured + args = profile_sync.build_parser().parse_args(cmd[2:]) + profile_sync._refuse_flag_combinations(args) + self.assertTrue(args.as_json) + def test_a_non_list_report_is_an_error_not_a_crash(self): findings = cf.summarize_profile_sync({"oops": 1}) self.assertEqual(findings[0].status, cf.ERROR) diff --git a/tests/test_profile_sync.py b/tests/test_profile_sync.py index aa47269c..754fd28c 100644 --- a/tests/test_profile_sync.py +++ b/tests/test_profile_sync.py @@ -4022,7 +4022,20 @@ class TestOutputModesApplyTheirFlags(unittest.TestCase): self.assertIn("prose under an older pin", out) def test_extras_are_refused_with_json(self): - code, _, err = self._main("--json", "--check-version") + code, _, err = self._main("--json", "--detect-new-files") + self.assertEqual(code, 1) + self.assertIn("--detect-new-files", err) + + def test_json_carries_the_version_check(self): + """check_freshness asks for --json --check-version: refused, its + profile row read an empty report as unreadable on every run.""" + code, out, err = self._main("--json", "--check-version") + self.assertEqual(code, 0, err) + rows = json.loads(out) + self.assertTrue(all("version" in row and "page_version" in row for row in rows)) + + def test_rankings_refuse_the_version_check(self): + code, _, err = self._main("--triage", "--check-version") self.assertEqual(code, 1) self.assertIn("--check-version", err)