From 0a9cd84098607b55582029f049d8bf2b910c34e5 Mon Sep 17 00:00:00 2001 From: Johan Lorenzo Date: Tue, 31 Mar 2026 12:07:47 +0200 Subject: [PATCH] landoscript: extend version_bump to handle JSON manifests (bug 2025695) --- .../src/landoscript/actions/version_bump.py | 24 ++++++- .../data/landoscript_task_schema.json | 1 - landoscript/tests/test_version_bump.py | 64 +++++++++++++++++++ 3 files changed, 85 insertions(+), 4 deletions(-) diff --git a/landoscript/src/landoscript/actions/version_bump.py b/landoscript/src/landoscript/actions/version_bump.py index d8f60641a..aff3a2240 100644 --- a/landoscript/src/landoscript/actions/version_bump.py +++ b/landoscript/src/landoscript/actions/version_bump.py @@ -1,7 +1,10 @@ +import json import logging import os.path +import re import typing from dataclasses import dataclass +from typing import Optional from gql.transport.exceptions import TransportError from mozilla_version.gecko import GeckoVersion @@ -21,6 +24,7 @@ ALLOWED_BUMP_FILES = ( "browser/config/version.txt", "browser/config/version_display.txt", + "browser/extensions/newtab/manifest.json", "config/milestone.txt", "mobile/android/version.txt", "mail/config/version.txt", @@ -30,8 +34,8 @@ @dataclass(frozen=True) class VersionBumpInfo: - next_version: str files: list[str] + next_version: Optional[str] = None async def run( @@ -85,9 +89,17 @@ async def run( log.info(f"{file}: Version bumping skipped due to unchanged values") continue - modified = orig.replace(str(cur), str(next_)) + if file.endswith(".json"): + modified = re.sub( + r'("version":\s*")' + re.escape(str(cur)) + r'"', + rf'\g<1>{next_}"', + orig, + ) + else: + modified = orig.replace(str(cur), str(next_)) if orig == modified: - raise LandoscriptError("file not modified, this should be impossible") + log.warning(f"{file}: version replacement produced no change, skipping") + continue log.info(f"{file}: successfully bumped! new contents are:") log_file_contents(modified) @@ -119,6 +131,12 @@ def extract_path(diff_text): def get_cur_and_next_version(filename, orig_contents, next_version, munge_next_version): + if filename.endswith(".json"): + manifest = json.loads(orig_contents) + cur = BaseVersion.parse(manifest["version"]) + next_ = cur.bump("minor_number") + return cur, next_ + VersionClass: BaseVersion = find_what_version_parser_to_use(filename) lines = [line for line in orig_contents.splitlines() if line and not line.startswith("#")] cur = VersionClass.parse(lines[-1]) diff --git a/landoscript/src/landoscript/data/landoscript_task_schema.json b/landoscript/src/landoscript/data/landoscript_task_schema.json index e046f6b77..d5caecf19 100644 --- a/landoscript/src/landoscript/data/landoscript_task_schema.json +++ b/landoscript/src/landoscript/data/landoscript_task_schema.json @@ -179,7 +179,6 @@ } }, "required": [ - "next_version", "files" ] }, diff --git a/landoscript/tests/test_version_bump.py b/landoscript/tests/test_version_bump.py index 8ec84f8dd..d7ec18cb1 100644 --- a/landoscript/tests/test_version_bump.py +++ b/landoscript/tests/test_version_bump.py @@ -394,4 +394,68 @@ def test_no_overlaps_in_version_classes(): def test_all_bump_files_have_version_class(): for bump_file in ALLOWED_BUMP_FILES: + # JSON manifests use BaseVersion directly and bypass the version class lookup + if bump_file.endswith(".json"): + continue assert any([bump_file.startswith(path) for path in _VERSION_CLASS_PER_BEGINNING_OF_PATH]) + + +MANIFEST_FILE = "browser/extensions/newtab/manifest.json" + + +def _manifest(version): + import json as _json + return _json.dumps({"manifest_version": 2, "name": "New Tab", "version": version}, indent=2) + + +@pytest.mark.asyncio +@pytest.mark.parametrize( + "initial_version,expected_version", + [ + pytest.param("151.0.0", "151.1.0", id="initial_after_merge_day"), + pytest.param("151.1.0", "151.2.0", id="normal_minor_bump"), + ], +) +async def test_json_manifest_bump(aioresponses, github_installation_responses, context, initial_version, expected_version): + payload = { + "actions": ["version_bump"], + "lando_repo": "repo_name", + "version_bump_info": { + "files": [MANIFEST_FILE], + }, + } + setup_github_graphql_responses(aioresponses, get_files_payload({MANIFEST_FILE: _manifest(initial_version)})) + await run_test( + aioresponses, + github_installation_responses, + context, + payload, + ["version_bump"], + assert_func=lambda req: assert_success( + req, + ["Automatic version bump", "NO BUG", "a=release", "CLOSED TREE"], + {MANIFEST_FILE: f' "version": "{initial_version}"'}, + {MANIFEST_FILE: f' "version": "{expected_version}"'}, + ), + ) + + +@pytest.mark.asyncio +async def test_json_manifest_file_not_found(aioresponses, github_installation_responses, context): + payload = { + "actions": ["version_bump"], + "lando_repo": "repo_name", + "version_bump_info": { + "files": [MANIFEST_FILE], + }, + } + setup_github_graphql_responses(aioresponses, get_files_payload({MANIFEST_FILE: None})) + await run_test( + aioresponses, + github_installation_responses, + context, + payload, + ["version_bump"], + err=LandoscriptError, + errmsg="does not exist", + )