diff --git a/package.json b/package.json index 2b3c0868..ff7106f3 100644 --- a/package.json +++ b/package.json @@ -42,7 +42,8 @@ "update:starters": "poetry run python ./scripts/update.py", "vendor:libs": "vite build --config vite.config.js", "vendor:libs:watch": "vite build --config vite.config.js --watch", - "vendor:update-and-build": "pnpm up katex mermaid markmap-autoloader alpinejs plotly.js preact --latest && pnpm vendor:libs" + "vendor:update-and-build": "pnpm up katex mermaid markmap-autoloader alpinejs plotly.js preact --latest && pnpm vendor:libs", + "test:releaser": "poetry run python scripts/test_release_modules.py" }, "browserslist": "> 1%" } diff --git a/scripts/release_modules.py b/scripts/release_modules.py index 4ebed28b..7ef408e4 100644 --- a/scripts/release_modules.py +++ b/scripts/release_modules.py @@ -294,6 +294,38 @@ def write_blox_tailwind_theme_version(module: Module, version: str) -> List[Path return updated +def update_citation_release_info(module: Module, version: str) -> List[Path]: + """Update the public citation metadata to the latest blox-tailwind release.""" + if module.name != "blox-tailwind": + return [] + + citation = REPO_ROOT / "CITATION.cff" + if not citation.exists(): + return [] + + original = citation.read_text(encoding="utf-8") + release_date = datetime.now(timezone.utc).date().isoformat() + + updated = original + version_pattern = r'(?m)^(version:\s*)"[^"]+"' + if re.search(version_pattern, updated): + updated = re.sub(version_pattern, rf'\g<1>"{version}"', updated) + else: + updated = f'{updated.rstrip()}\nversion: "{version}"\n' + + date_pattern = r'(?m)^(date-released:\s*)(.+)$' + if re.search(date_pattern, updated): + updated = re.sub(date_pattern, rf"\g<1>{release_date}", updated) + else: + updated = f"{updated.rstrip()}\ndate-released: {release_date}\n" + + if updated != original: + citation.write_text(updated, encoding="utf-8") + logging.info("updated CITATION.cff -> version %s, date %s", version, release_date) + return [citation] + return [] + + def update_go_mod_require_version(go_mod_text: str, dep_module_path: str, new_version: str) -> str: # Replace exact require line if present # Handles both single-line require and block form @@ -686,8 +718,9 @@ def main() -> None: go_mod_synced = update_module_requirements_to_latest(m, modules, latest_versions) if go_mod_synced: touched_files.append(go_mod_synced) - # Special-case blox-tailwind theme version bump + # Special-case blox-tailwind theme version bump and citation metadata touched_files.extend(write_blox_tailwind_theme_version(m, next_version)) + touched_files.extend(update_citation_release_info(m, next_version)) # Stage and commit any pre-release file changes if touched_files: diff --git a/scripts/test_release_modules.py b/scripts/test_release_modules.py new file mode 100644 index 00000000..84611c68 --- /dev/null +++ b/scripts/test_release_modules.py @@ -0,0 +1,139 @@ +#!/usr/bin/env python3 + +import sys +import tempfile +import unittest +from pathlib import Path + +ROOT = Path(__file__).resolve().parents[1] +SCRIPTS_DIR = ROOT / "scripts" +if str(SCRIPTS_DIR) not in sys.path: + sys.path.insert(0, str(SCRIPTS_DIR)) + +try: + import requests # type: ignore +except ModuleNotFoundError: + import types + + class _DummyResponse: + status_code = 0 + + def json(self): + return {} + + requests = types.SimpleNamespace(get=lambda *args, **kwargs: _DummyResponse()) + sys.modules["requests"] = requests + +try: + import tomlkit # type: ignore +except ModuleNotFoundError: + import types + + def _noop_parse(content: str): + return {} + + def _noop_dumps(data): + return "" + + tomlkit = types.SimpleNamespace(parse=_noop_parse, dumps=_noop_dumps) + sys.modules["tomlkit"] = tomlkit + +try: + from ruamel.yaml import YAML # type: ignore + from ruamel.yaml.scalarstring import SingleQuotedScalarString # type: ignore +except ModuleNotFoundError: + import types + + class _DummyYAML: + preserve_quotes = False + + def load(self, stream): + return {} + + def dump(self, data, stream=None): + if stream and hasattr(stream, "write"): + stream.write("") + return "" + + class _DummyScalarString(str): + pass + + ruamel_module = types.ModuleType("ruamel") + ruamel_yaml_module = types.ModuleType("ruamel.yaml") + ruamel_yaml_scalar_module = types.ModuleType("ruamel.yaml.scalarstring") + ruamel_yaml_module.YAML = _DummyYAML + ruamel_yaml_scalar_module.SingleQuotedScalarString = _DummyScalarString + ruamel_module.yaml = ruamel_yaml_module + sys.modules["ruamel"] = ruamel_module + sys.modules["ruamel.yaml"] = ruamel_yaml_module + sys.modules["ruamel.yaml.scalarstring"] = ruamel_yaml_scalar_module + YAML = _DummyYAML + SingleQuotedScalarString = _DummyScalarString + +import release_modules + + +class UpdateCitationReleaseInfoTests(unittest.TestCase): + def setUp(self) -> None: + self.original_repo_root = release_modules.REPO_ROOT + self.original_datetime = release_modules.datetime + + def tearDown(self) -> None: + release_modules.REPO_ROOT = self.original_repo_root + release_modules.datetime = self.original_datetime + + def _patch_repo_root_with_citation(self, content: str) -> Path: + tmp_dir = tempfile.TemporaryDirectory() + self.addCleanup(tmp_dir.cleanup) + repo_root = Path(tmp_dir.name) + (repo_root / "CITATION.cff").write_text(content, encoding="utf-8") + release_modules.REPO_ROOT = repo_root + return repo_root + + def test_updates_version_and_date_for_blox_tailwind(self) -> None: + repo_root = self._patch_repo_root_with_citation( + 'version: "0.10.0"\ndate-released: 2024-01-01\n', + ) + + original_datetime = release_modules.datetime + + class FixedDatetime(original_datetime): + @classmethod + def now(cls, tz=None): + return cls(2024, 2, 3, tzinfo=tz) + + release_modules.datetime = FixedDatetime + + module = release_modules.Module( + name="blox-tailwind", + rel_dir=Path("modules/blox-tailwind"), + module_path="modules/blox-tailwind", + major=0, + ) + + updated_paths = release_modules.update_citation_release_info(module, "1.2.3") + self.assertEqual(updated_paths, [repo_root / "CITATION.cff"]) + + contents = (repo_root / "CITATION.cff").read_text(encoding="utf-8") + self.assertIn('version: "1.2.3"', contents) + self.assertIn("date-released: 2024-02-03", contents) + + def test_noop_for_non_blox_tailwind_module(self) -> None: + repo_root = self._patch_repo_root_with_citation('version: "0.10.0"\n') + + module = release_modules.Module( + name="other-module", + rel_dir=Path("modules/other-module"), + module_path="modules/other-module", + major=0, + ) + + result = release_modules.update_citation_release_info(module, "9.9.9") + self.assertEqual(result, []) + + contents = (repo_root / "CITATION.cff").read_text(encoding="utf-8") + self.assertEqual(contents, 'version: "0.10.0"\n') + + +if __name__ == "__main__": + unittest.main()