mirror of
https://github.com/rookiestar28/ComfyUI-OpenClaw.git
synced 2026-08-14 00:48:07 +00:00
fix(config): harden pack version fallback
This commit is contained in:
@@ -42,22 +42,31 @@ PACK_NAME = "ComfyUI-OpenClaw"
|
||||
PACK_START_TIME = time.time()
|
||||
|
||||
|
||||
def _read_pyproject_version() -> Optional[str]:
|
||||
"""
|
||||
Read version from pyproject.toml ([project].version) as the single source of truth.
|
||||
def _extract_toml_section(text: str, header: str) -> Optional[str]:
|
||||
pattern = re.compile(
|
||||
rf"(?ms)^\ufeff?\[{re.escape(header)}\]\s*$\n(?P<body>.*?)(?=^\[|\Z)"
|
||||
)
|
||||
match = pattern.search(text)
|
||||
if not match:
|
||||
return None
|
||||
return match.group("body")
|
||||
|
||||
Uses a lightweight regex parse to avoid non-stdlib TOML dependencies.
|
||||
"""
|
||||
try:
|
||||
pack_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
pyproject_path = os.path.join(pack_dir, "pyproject.toml")
|
||||
if not os.path.exists(pyproject_path):
|
||||
return None
|
||||
text = ""
|
||||
with open(pyproject_path, "r", encoding="utf-8") as f:
|
||||
text = f.read()
|
||||
|
||||
# Prefer stdlib TOML parser if available (Python 3.11+), then fallback to regex.
|
||||
def _extract_toml_string_assignment(section_text: str, key: str) -> Optional[str]:
|
||||
match = re.search(
|
||||
rf"(?m)^\s*{re.escape(key)}\s*=\s*['\"]([^'\"]+)['\"]\s*$",
|
||||
section_text,
|
||||
)
|
||||
if not match:
|
||||
return None
|
||||
value = match.group(1).strip()
|
||||
return value or None
|
||||
|
||||
|
||||
def _parse_pyproject_version_text(
|
||||
text: str, *, prefer_tomllib: bool = True
|
||||
) -> Optional[str]:
|
||||
if prefer_tomllib:
|
||||
try:
|
||||
from tomllib import loads as _toml_loads # type: ignore
|
||||
except Exception:
|
||||
@@ -72,21 +81,41 @@ def _read_pyproject_version() -> Optional[str]:
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# Find the [project] section and parse `version = "..."` within it.
|
||||
# IMPORTANT: tolerate BOM/CRLF so the UI version does not silently fall back to 0.1.0.
|
||||
# This is intentionally conservative to avoid false matches in other sections.
|
||||
m = re.search(
|
||||
r"(?ms)^\ufeff?\\[project\\]\\s*(?:[^\\[]*?)^version\\s*=\\s*[\"']([^\"']+)[\"']\\s*$",
|
||||
text,
|
||||
)
|
||||
if not m:
|
||||
# IMPORTANT: keep this fallback section-bounded.
|
||||
# Matching any `version = ...` outside `[project]` silently reports the wrong build.
|
||||
project_section = _extract_toml_section(text, "project")
|
||||
if project_section is None:
|
||||
return None
|
||||
return _extract_toml_string_assignment(project_section, "version")
|
||||
|
||||
|
||||
def _read_pyproject_version_from_path(
|
||||
pyproject_path: os.PathLike[str] | str, *, prefer_tomllib: bool = True
|
||||
) -> Optional[str]:
|
||||
"""
|
||||
Read version from pyproject.toml ([project].version) as the single source of truth.
|
||||
|
||||
Uses a lightweight regex parse to avoid non-stdlib TOML dependencies.
|
||||
"""
|
||||
try:
|
||||
pyproject_path = os.fspath(pyproject_path)
|
||||
if not os.path.exists(pyproject_path):
|
||||
return None
|
||||
ver = (m.group(1) or "").strip()
|
||||
return ver or None
|
||||
with open(pyproject_path, "r", encoding="utf-8") as f:
|
||||
text = f.read()
|
||||
return _parse_pyproject_version_text(
|
||||
text,
|
||||
prefer_tomllib=prefer_tomllib,
|
||||
)
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
|
||||
def _read_pyproject_version() -> Optional[str]:
|
||||
pack_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
return _read_pyproject_version_from_path(os.path.join(pack_dir, "pyproject.toml"))
|
||||
|
||||
|
||||
# Version: single source of truth is pyproject.toml (line 4 in this repo).
|
||||
PACK_VERSION = _read_pyproject_version() or "0.1.0"
|
||||
|
||||
|
||||
@@ -60,7 +60,9 @@ def _validate_skip_policy(repo_root: Path, path: Path) -> List[str]:
|
||||
if not isinstance(modules, list) or any(
|
||||
not isinstance(item, str) or not item.strip() for item in modules
|
||||
):
|
||||
failures.append("skip policy: no_skip_modules must be a list of non-empty strings")
|
||||
failures.append(
|
||||
"skip policy: no_skip_modules must be a list of non-empty strings"
|
||||
)
|
||||
return failures
|
||||
|
||||
seen = set()
|
||||
@@ -90,7 +92,9 @@ def _validate_skip_policy(repo_root: Path, path: Path) -> List[str]:
|
||||
return failures
|
||||
|
||||
metadata_keys = {str(key).strip() for key in metadata.keys()}
|
||||
missing_metadata = [module for module in normalized_modules if module not in metadata_keys]
|
||||
missing_metadata = [
|
||||
module for module in normalized_modules if module not in metadata_keys
|
||||
]
|
||||
extra_metadata = sorted(
|
||||
key for key in metadata_keys if key and key not in set(normalized_modules)
|
||||
)
|
||||
@@ -153,7 +157,10 @@ def _validate_mutation_allowlist(repo_root: Path, path: Path) -> List[str]:
|
||||
if duplicates:
|
||||
failures.append(
|
||||
"mutation allowlist: duplicate (file, mutation_index) entries: "
|
||||
+ ", ".join(f"{file}@{mutation_index}" for file, mutation_index in sorted(duplicates))
|
||||
+ ", ".join(
|
||||
f"{file}@{mutation_index}"
|
||||
for file, mutation_index in sorted(duplicates)
|
||||
)
|
||||
)
|
||||
return failures
|
||||
|
||||
@@ -171,10 +178,14 @@ def verify_test_debt_governance(
|
||||
try:
|
||||
failures.extend(_validate_skip_policy(repo_root, skip_policy_path))
|
||||
except Exception as exc:
|
||||
failures.append(f"skip policy: failed to validate {skip_policy_path}: {exc}")
|
||||
failures.append(
|
||||
f"skip policy: failed to validate {skip_policy_path}: {exc}"
|
||||
)
|
||||
|
||||
if not mutation_allowlist_path.is_file():
|
||||
failures.append(f"missing mutation survivor allowlist: {mutation_allowlist_path}")
|
||||
failures.append(
|
||||
f"missing mutation survivor allowlist: {mutation_allowlist_path}"
|
||||
)
|
||||
else:
|
||||
try:
|
||||
failures.extend(
|
||||
|
||||
@@ -3,7 +3,6 @@ import textwrap
|
||||
from pathlib import Path
|
||||
from typing import Any, Dict, Iterable, Optional
|
||||
|
||||
|
||||
DEFAULT_REQUIRED_HOTSPOT_FAMILIES = [
|
||||
"safe_io",
|
||||
"security_boundary",
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
import tempfile
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
|
||||
import config
|
||||
|
||||
|
||||
class TestR175PackVersionFallback(unittest.TestCase):
|
||||
def test_fallback_parser_handles_bom_crlf_and_double_quotes(self):
|
||||
text = '\ufeff[project]\r\nname = "ComfyUI-OpenClaw"\r\nversion = "9.9.9"\r\n'
|
||||
self.assertEqual(
|
||||
config._parse_pyproject_version_text(text, prefer_tomllib=False),
|
||||
"9.9.9",
|
||||
)
|
||||
|
||||
def test_fallback_parser_handles_single_quotes_and_spacing(self):
|
||||
text = "[project]\nname='ComfyUI-OpenClaw'\nversion = '1.2.3'\n"
|
||||
self.assertEqual(
|
||||
config._parse_pyproject_version_text(text, prefer_tomllib=False),
|
||||
"1.2.3",
|
||||
)
|
||||
|
||||
def test_fallback_parser_returns_none_when_project_section_missing(self):
|
||||
text = '[tool.black]\nline-length = 88\nversion = "7.7.7"\n'
|
||||
self.assertIsNone(
|
||||
config._parse_pyproject_version_text(text, prefer_tomllib=False)
|
||||
)
|
||||
|
||||
def test_read_version_from_path_returns_none_for_missing_file(self):
|
||||
with tempfile.TemporaryDirectory() as tmpdir:
|
||||
missing = Path(tmpdir) / "missing.toml"
|
||||
self.assertIsNone(
|
||||
config._read_pyproject_version_from_path(
|
||||
missing, prefer_tomllib=False
|
||||
)
|
||||
)
|
||||
|
||||
def test_repo_pack_version_matches_pyproject_source_of_truth(self):
|
||||
pyproject = Path(__file__).resolve().parents[1] / "pyproject.toml"
|
||||
expected = config._read_pyproject_version_from_path(pyproject)
|
||||
self.assertEqual(config.PACK_VERSION, expected)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Reference in New Issue
Block a user