Harden JSON parsing, collections, continuity diffs, manifests, storyboard expansion, numeric handling, quality gates, rankings, scheduling, merge diagnostics, migrations, retries, checkpoints, hashing, environment locks, reproducible packaging, release validation, frontend rollback, and regression coverage.
113 lines
4.1 KiB
Python
113 lines
4.1 KiB
Python
from __future__ import annotations
|
|
|
|
import ast
|
|
import importlib.util
|
|
import json
|
|
import re
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
ROOT = Path(__file__).resolve().parents[1]
|
|
REQUIRED = [
|
|
"VERSION",
|
|
"__init__.py",
|
|
"nodes.py",
|
|
"extended_nodes.py",
|
|
"environment_nodes.py",
|
|
"continuity_core.py",
|
|
"production_core.py",
|
|
"runtime_core.py",
|
|
"collaboration_core.py",
|
|
"validation_core.py",
|
|
"js/continuity_director.js",
|
|
"js/reliability_panel.js",
|
|
"js/continuity_director.css",
|
|
"pyproject.toml",
|
|
"README.md",
|
|
"CHANGELOG.md",
|
|
"ROADMAP.md",
|
|
"LICENSE",
|
|
".github/workflows/ci.yml",
|
|
".github/workflows/release.yml",
|
|
".github/workflows/publish.yml",
|
|
]
|
|
|
|
|
|
def fail(message: str) -> None:
|
|
raise SystemExit(f"release validation failed: {message}")
|
|
|
|
|
|
def read(path: str) -> str:
|
|
return (ROOT / path).read_text(encoding="utf-8")
|
|
|
|
|
|
for path in REQUIRED:
|
|
if not (ROOT / path).is_file():
|
|
fail(f"missing {path}")
|
|
|
|
version = read("VERSION").strip()
|
|
if not re.fullmatch(r"\d+\.\d+\.\d+", version):
|
|
fail(f"invalid VERSION value: {version!r}")
|
|
|
|
for path in list(ROOT.glob("*.py")) + list((ROOT / "scripts").glob("*.py")):
|
|
ast.parse(path.read_text(encoding="utf-8"), filename=str(path))
|
|
|
|
if (ROOT / ".bootstrap").exists() and any((ROOT / ".bootstrap").iterdir()):
|
|
fail("legacy bootstrap artifacts remain")
|
|
|
|
spec = importlib.util.spec_from_file_location("continuity_director_release", ROOT / "__init__.py", submodule_search_locations=[str(ROOT)])
|
|
if spec is None or spec.loader is None:
|
|
fail("unable to import package")
|
|
module = importlib.util.module_from_spec(spec)
|
|
sys.modules[spec.name] = module
|
|
spec.loader.exec_module(module)
|
|
|
|
if getattr(module, "__version__", None) != version:
|
|
fail("Python package version does not match VERSION")
|
|
if len(module.NODE_CLASS_MAPPINGS) != 20:
|
|
fail(f"expected 20 registered nodes, found {len(module.NODE_CLASS_MAPPINGS)}")
|
|
if set(module.NODE_CLASS_MAPPINGS) != set(module.NODE_DISPLAY_NAME_MAPPINGS):
|
|
fail("display mappings do not match node mappings")
|
|
|
|
for node_name, node_class in module.NODE_CLASS_MAPPINGS.items():
|
|
if not callable(getattr(node_class, "INPUT_TYPES", None)):
|
|
fail(f"{node_name} is missing INPUT_TYPES")
|
|
for attribute in ("FUNCTION", "RETURN_TYPES", "CATEGORY"):
|
|
if not getattr(node_class, attribute, None):
|
|
fail(f"{node_name} is missing {attribute}")
|
|
if not (ROOT / "js" / "docs" / f"{node_name}.md").is_file():
|
|
fail(f"missing fallback docs for {node_name}")
|
|
|
|
pyproject = read("pyproject.toml")
|
|
project_match = re.search(r'^version\s*=\s*"([^"]+)"', pyproject, re.MULTILINE)
|
|
if not project_match or project_match.group(1) != version:
|
|
fail("pyproject version does not match VERSION")
|
|
|
|
frontend = read("js/continuity_director.js")
|
|
frontend_match = re.search(r'const VER\s*=\s*"([^"]+)"', frontend)
|
|
if not frontend_match or frontend_match.group(1) != version:
|
|
fail("frontend version does not match VERSION")
|
|
|
|
readme = read("README.md")
|
|
if f"version-{version}-" not in readme or f"continuity-director-v{version}.zip" not in readme:
|
|
fail("README release references do not match VERSION")
|
|
if not (ROOT / "docs" / "releases" / f"v{version}.md").is_file():
|
|
fail(f"missing release notes for v{version}")
|
|
|
|
release_workflow = read(".github/workflows/release.yml")
|
|
if "steps.version.outputs.version" not in release_workflow:
|
|
fail("release workflow does not derive artifact paths from VERSION")
|
|
publish_workflow = read(".github/workflows/publish.yml")
|
|
for marker in ("scripts/build_release.py", "sha256sum", "softprops/action-gh-release@v2"):
|
|
if marker not in publish_workflow:
|
|
fail(f"publish workflow is missing {marker}")
|
|
|
|
storyboard = json.loads(read("examples/starter_storyboard.json"))
|
|
if not isinstance(storyboard.get("shots"), list) or not storyboard["shots"]:
|
|
fail("starter storyboard has no shots")
|
|
for file_name in ("continuity_director.js", "reliability_panel.js"):
|
|
if "app.registerExtension" not in read(f"js/{file_name}"):
|
|
fail(f"{file_name} does not register an extension")
|
|
|
|
print(f"release validation passed: v{version}, 20 nodes, version consistency, workflows, docs, and package metadata")
|