chore(release): document hardening and verify complete source package composition
This commit is contained in:
@@ -47,6 +47,8 @@ cd "$META_ROOT"
|
||||
"$PYTHON" tools/inventory/platform-interface-inventory.py --strict-declarations --strict-endpoints
|
||||
"$PYTHON" tools/repo/sync-module-package-workflows.py --check
|
||||
"$PYTHON" tools/release/generate-developer-meta-package.py --check
|
||||
"$PYTHON" tools/checks/check-webui-package-facades.py
|
||||
"$PYTHON" -m unittest tests.test_webui_package_facades
|
||||
"$PYTHON" -m unittest tests.test_module_package_workflows tests.test_package_registry_release
|
||||
"$PYTHON" -m unittest tests.test_deployment_installer
|
||||
"$PYTHON" -m unittest tests.test_capability_fit_evidence
|
||||
|
||||
@@ -0,0 +1,101 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Check that release Git dependencies expose their owning WebUI package."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
from pathlib import Path
|
||||
import re
|
||||
|
||||
|
||||
META_ROOT = Path(__file__).resolve().parents[2]
|
||||
GIT_REPOSITORY = re.compile(r"/(govoplan-[a-z0-9-]+)\.git#v[^/]+$")
|
||||
PARITY_FIELDS = (
|
||||
"name", "version", "type", "dependencies", "optionalDependencies",
|
||||
"peerDependencies", "peerDependenciesMeta",
|
||||
)
|
||||
ENTRY_FIELDS = ("main", "module", "types", "exports")
|
||||
|
||||
|
||||
def prefixed_entries(value: object) -> object:
|
||||
if isinstance(value, str):
|
||||
return "./webui/" + value[2:] if value.startswith("./") else "webui/" + value
|
||||
if isinstance(value, dict):
|
||||
return {key: prefixed_entries(item) for key, item in value.items()}
|
||||
if isinstance(value, list):
|
||||
return [prefixed_entries(item) for item in value]
|
||||
return value
|
||||
|
||||
|
||||
def entry_paths(value: object) -> list[str]:
|
||||
if isinstance(value, str):
|
||||
return [value]
|
||||
if isinstance(value, dict):
|
||||
return [path for item in value.values() for path in entry_paths(item)]
|
||||
if isinstance(value, list):
|
||||
return [path for item in value for path in entry_paths(item)]
|
||||
return []
|
||||
|
||||
|
||||
def facade_issues(repository: Path, *, package_name: str) -> list[str]:
|
||||
issues: list[str] = []
|
||||
try:
|
||||
root = json.loads((repository / "package.json").read_text(encoding="utf-8"))
|
||||
webui = json.loads((repository / "webui/package.json").read_text(encoding="utf-8"))
|
||||
except (OSError, ValueError) as exc:
|
||||
return [f"{repository.name}: cannot read package facades: {exc}"]
|
||||
if not isinstance(root, dict) or not isinstance(webui, dict):
|
||||
return [f"{repository.name}: package manifests must be JSON objects"]
|
||||
if webui.get("name") != package_name:
|
||||
issues.append(f"{repository.name}: WebUI name does not match {package_name}")
|
||||
for field in PARITY_FIELDS:
|
||||
if root.get(field) != webui.get(field):
|
||||
issues.append(f"{repository.name}: root {field} differs from owning WebUI package")
|
||||
for field in ENTRY_FIELDS:
|
||||
expected = prefixed_entries(webui.get(field))
|
||||
if root.get(field) != expected:
|
||||
issues.append(f"{repository.name}: root {field} must target the corresponding webui/ entry")
|
||||
for entry in entry_paths(root.get(field)):
|
||||
path = repository / entry
|
||||
if not path.resolve().is_relative_to((repository / "webui").resolve()):
|
||||
issues.append(f"{repository.name}: {field} entry escapes webui/: {entry}")
|
||||
elif "*" not in entry and not path.is_file():
|
||||
issues.append(f"{repository.name}: missing {field} entry: {entry}")
|
||||
if not root.get("exports") and not root.get("main"):
|
||||
issues.append(f"{repository.name}: root package has no WebUI entry point")
|
||||
return issues
|
||||
|
||||
|
||||
def check_composition(workspace: Path, *, core_root: Path | None = None) -> tuple[int, list[str]]:
|
||||
core = core_root or workspace / "govoplan-core"
|
||||
release = json.loads((core / "webui/package.release.json").read_text(encoding="utf-8"))
|
||||
checked = 0
|
||||
issues: list[str] = []
|
||||
for name, reference in release.get("dependencies", {}).items():
|
||||
if not name.startswith("@govoplan/"):
|
||||
continue
|
||||
match = GIT_REPOSITORY.search(reference) if isinstance(reference, str) else None
|
||||
if match is None:
|
||||
issues.append(f"{name}: release dependency is not a versioned GovOPlaN Git source")
|
||||
continue
|
||||
checked += 1
|
||||
issues.extend(facade_issues(workspace / match.group(1), package_name=name))
|
||||
return checked, issues
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument("--workspace-root", type=Path, default=META_ROOT.parent)
|
||||
parser.add_argument("--core-root", type=Path)
|
||||
args = parser.parse_args()
|
||||
checked, issues = check_composition(args.workspace_root, core_root=args.core_root)
|
||||
if issues:
|
||||
print("\n".join(issues))
|
||||
return 1
|
||||
print(f"Release WebUI package facade checks passed for {checked} Git dependencies")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
Reference in New Issue
Block a user