feat(devkit): add resumable workspace automation and UI review tooling
Verified with the coordinated workspace changes by devkit full run 2026-09-08T225814-186389-0000-3e3ed7cd (all seven phases passed). This shared UI pass does not mark the individual module reviews complete.
This commit is contained in:
+173
@@ -0,0 +1,173 @@
|
||||
"""Bounded package metadata and conservative direct-Node test discovery."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
import re
|
||||
import shlex
|
||||
|
||||
from .common import digest, read_json, redact_argv, reject_symlinks
|
||||
|
||||
MAX_PACKAGE_BYTES = 1024 * 1024
|
||||
MAX_SCRIPTS = 512
|
||||
CORE_COMPONENT_SUITES = (
|
||||
"data-grid-actions",
|
||||
"dialog-focus",
|
||||
"explorer-tree",
|
||||
"icon-button",
|
||||
"layout-primitives",
|
||||
"mail-components",
|
||||
"metric-card",
|
||||
"page-layout",
|
||||
"workspace-layout",
|
||||
"people-picker",
|
||||
"password-field",
|
||||
"resource-access",
|
||||
"action-blocker",
|
||||
"documentation-help",
|
||||
"selection-list",
|
||||
"wysiwyg-editor",
|
||||
)
|
||||
CORE_RUNNER = "scripts/run-component-tests.mjs"
|
||||
|
||||
|
||||
def read_package(path: Path) -> dict:
|
||||
try:
|
||||
package = read_json(path, max_bytes=MAX_PACKAGE_BYTES)
|
||||
except (OSError, ValueError) as exc:
|
||||
raise ValueError(f"Cannot safely read package metadata: {path}") from exc
|
||||
if not isinstance(package, dict):
|
||||
raise ValueError(f"Package metadata must be an object: {path}")
|
||||
scripts = package.get("scripts", {})
|
||||
if not isinstance(scripts, dict) or len(scripts) > MAX_SCRIPTS:
|
||||
raise ValueError(f"Package scripts must be a bounded object: {path}")
|
||||
for name, command in scripts.items():
|
||||
if (
|
||||
not isinstance(name, str)
|
||||
or not 1 <= len(name) <= 128
|
||||
or any(char in name for char in "\0\r\n")
|
||||
):
|
||||
raise ValueError(f"Package script names must be bounded strings: {path}")
|
||||
if (
|
||||
not isinstance(command, str)
|
||||
or not 1 <= len(command) <= 8192
|
||||
or "\0" in command
|
||||
):
|
||||
raise ValueError(
|
||||
f"Package script commands must be bounded nonempty strings: {path}"
|
||||
)
|
||||
return package
|
||||
|
||||
|
||||
def core_component_alias(
|
||||
repo_name: str, name: str, command: str, package_path: Path
|
||||
) -> str | None:
|
||||
if repo_name != "govoplan-core" or package_path.parent.name != "webui":
|
||||
return None
|
||||
if name == "test:components" and command == f"node {CORE_RUNNER}":
|
||||
return "all"
|
||||
for suite in CORE_COMPONENT_SUITES:
|
||||
if name == "test:" + suite and command == f"node {CORE_RUNNER} {suite}":
|
||||
return suite
|
||||
return None
|
||||
|
||||
|
||||
def direct_node(command: str, package_path: Path) -> tuple[list[str] | None, str]:
|
||||
try:
|
||||
parts = shlex.split(command)
|
||||
except ValueError:
|
||||
return None, "Malformed command quoting; no command was guessed or executed."
|
||||
if not parts or parts[0] != "node":
|
||||
return (
|
||||
None,
|
||||
"Not a direct Node source test; use its explicitly reviewed owning workflow.",
|
||||
)
|
||||
offset = 2 if len(parts) > 1 and parts[1] == "--test" else 1
|
||||
if len(parts) != offset + 1 or not re.fullmatch(
|
||||
r"(?:scripts|tests)/[A-Za-z0-9_.-]+\.mjs", parts[offset]
|
||||
):
|
||||
return (
|
||||
None,
|
||||
"Compound command, flags or arguments are unsupported by scoped source discovery.",
|
||||
)
|
||||
if parts[offset] == CORE_RUNNER:
|
||||
return (
|
||||
None,
|
||||
"Only exact known Core component aliases belong to the shared UI batch.",
|
||||
)
|
||||
target = package_path.parent / parts[offset]
|
||||
try:
|
||||
reject_symlinks(target)
|
||||
if not target.is_file() or not target.resolve().is_relative_to(
|
||||
package_path.parent.resolve()
|
||||
):
|
||||
return None, "Declared test target is missing or escapes its package."
|
||||
except (OSError, ValueError):
|
||||
return None, "Declared test target is not a safe regular package file."
|
||||
return [
|
||||
"{node}",
|
||||
*parts[1:offset],
|
||||
str(target),
|
||||
], "Direct package-owned source test."
|
||||
|
||||
|
||||
def declared_tests(repo, package_path: Path) -> list[dict]:
|
||||
package = read_package(package_path)
|
||||
result = []
|
||||
for name, command in sorted(package.get("scripts", {}).items()):
|
||||
if name != "test" and not name.startswith("test:"):
|
||||
continue
|
||||
component = core_component_alias(repo.name, name, command, package_path)
|
||||
argv, reason = (
|
||||
direct_node(command, package_path)
|
||||
if component is None
|
||||
else (None, "Exact known Core component alias.")
|
||||
)
|
||||
result.append(
|
||||
{
|
||||
"repo": repo.name,
|
||||
"package_path": str(package_path),
|
||||
"name": name,
|
||||
"command_sha256": digest(command),
|
||||
"argv": redact_argv(argv) if argv else None,
|
||||
"component_suite": component,
|
||||
"reason": reason,
|
||||
"_argv": argv,
|
||||
}
|
||||
)
|
||||
return result
|
||||
|
||||
|
||||
def discovered_sources(repo) -> list[dict]:
|
||||
webui = repo.path / "webui"
|
||||
found = {}
|
||||
for folder in ("scripts", "tests"):
|
||||
for pattern in ("test-interface-pattern*.mjs", "*structure*.mjs"):
|
||||
for target in (webui / folder).glob(pattern):
|
||||
command = (
|
||||
"node "
|
||||
+ ("--test " if target.name.endswith(".test.mjs") else "")
|
||||
+ target.relative_to(webui).as_posix()
|
||||
)
|
||||
argv, reason = direct_node(command, webui / "package.json")
|
||||
found[str(target)] = {
|
||||
"repo": repo.name,
|
||||
"package_path": str(webui / "package.json"),
|
||||
"name": "file:" + target.relative_to(webui).as_posix(),
|
||||
"discovered": True,
|
||||
"command_sha256": digest(command),
|
||||
"argv": redact_argv(argv) if argv else None,
|
||||
"component_suite": None,
|
||||
"reason": reason,
|
||||
"_argv": argv,
|
||||
}
|
||||
if len(found) > MAX_SCRIPTS:
|
||||
raise ValueError(
|
||||
f"Too many discovered source checks in {repo.name}"
|
||||
)
|
||||
return [found[key] for key in sorted(found)]
|
||||
|
||||
|
||||
def source_stage_id(repo_name: str, argv: list[str]) -> str:
|
||||
stem = Path(argv[-1]).stem
|
||||
return f"{repo_name}.{stem}"[:110] + "." + digest(argv)[:12]
|
||||
Reference in New Issue
Block a user