Publish complete signed module catalogs
Dependency Audit / dependency-audit (push) Successful in 1m46s
Deployment Installer / deployment-installer (push) Successful in 5s
Security Audit / security-audit (push) Successful in 11m49s

This commit is contained in:
2026-08-06 21:13:33 +02:00
parent f3cfd1bccc
commit 09046e6e62
17 changed files with 666 additions and 394 deletions
+64 -1
View File
@@ -21,6 +21,10 @@ GIT_REQUIREMENT = re.compile(
r"(?P<version>[A-Za-z0-9._+!-]+))$"
)
LOCAL_CORE = re.compile(r"^(?:-e\s+)?\.\./govoplan-core(?:\[(?P<extras>[^]]+)\])?$")
EXACT_PACKAGE = re.compile(
r"^(?P<package>govoplan(?:-[a-z0-9-]+)?)(?:\[(?P<extras>[^]]+)\])?=="
r"(?P<version>[A-Za-z0-9._+!-]+)$"
)
class PackageSetError(ValueError):
@@ -38,6 +42,17 @@ def build_parser() -> argparse.ArgumentParser:
type=Path,
default=META_ROOT / "requirements-release.txt",
)
parser.add_argument(
"--profile",
choices=("base", "full"),
default="base",
help="Base runtime roots or every package selected by govoplan[full].",
)
parser.add_argument(
"--meta-package",
type=Path,
default=META_ROOT / "packages" / "govoplan-meta" / "pyproject.toml",
)
parser.add_argument("--workspace", type=Path, default=META_ROOT.parent)
parser.add_argument("--output", type=Path, required=True)
return parser
@@ -79,11 +94,46 @@ def parse_release_requirements(path: Path, *, core_version: str) -> tuple[dict[s
return tuple(values)
def parse_meta_package(path: Path, *, core_version: str) -> tuple[dict[str, object], ...]:
project = tomllib.loads(path.read_text(encoding="utf-8")).get("project")
if not isinstance(project, dict):
raise PackageSetError("developer meta-package has no [project] table")
if str(project.get("name") or "") != "govoplan":
raise PackageSetError("developer meta-package has an unexpected project name")
if str(project.get("version") or "").removeprefix("v") != core_version.removeprefix("v"):
raise PackageSetError("developer meta-package version does not match Core")
optional = project.get("optional-dependencies")
full = optional.get("full") if isinstance(optional, dict) else None
dependencies = project.get("dependencies")
if not isinstance(dependencies, list) or not isinstance(full, list):
raise PackageSetError("developer meta-package must declare dependencies and the full extra")
values: list[dict[str, object]] = []
for raw in (*dependencies, *full):
if not isinstance(raw, str) or (match := EXACT_PACKAGE.fullmatch(raw.strip())) is None:
raise PackageSetError(f"developer meta-package requirement is not exact: {raw!r}")
package = match.group("package")
repository = "govoplan-core" if package == "govoplan-core" else package
values.append(
{
"name": package,
"version": match.group("version"),
"repository": repository,
"extras": _extras(match.group("extras")),
}
)
names = [str(item["name"]) for item in values]
if names.count("govoplan-core") != 1 or len(names) != len(set(names)):
raise PackageSetError("developer meta-package must contain one Core and unique packages")
return tuple(values)
def generate_package_set(
*,
core_version: str,
requirements: Path,
workspace: Path,
profile: str = "base",
meta_package: Path | None = None,
) -> dict[str, object]:
core_version = core_version.removeprefix("v")
if VERSION.fullmatch(core_version) is None:
@@ -91,7 +141,17 @@ def generate_package_set(
python_packages: list[dict[str, object]] = []
webui_packages: list[dict[str, object]] = []
seen_webui: set[str] = set()
for requirement in parse_release_requirements(requirements, core_version=core_version):
if profile not in {"base", "full"}:
raise PackageSetError(f"unsupported release profile: {profile}")
selected = (
parse_meta_package(
meta_package or META_ROOT / "packages" / "govoplan-meta" / "pyproject.toml",
core_version=core_version,
)
if profile == "full"
else parse_release_requirements(requirements, core_version=core_version)
)
for requirement in selected:
repository = workspace / str(requirement["repository"])
tag = f"v{requirement['version']}"
if not (repository / ".git").is_dir():
@@ -134,6 +194,7 @@ def generate_package_set(
payload: dict[str, object] = {
"schema_version": "1",
"release_version": core_version,
"profile": profile,
"registries": {
"python": "https://git.add-ideas.de/api/packages/GovOPlaN/pypi/simple",
"npm": "https://git.add-ideas.de/api/packages/GovOPlaN/npm/",
@@ -181,6 +242,8 @@ def main() -> int:
core_version=version,
requirements=args.requirements.expanduser().resolve(),
workspace=workspace,
profile=args.profile,
meta_package=args.meta_package.expanduser().resolve(),
)
except (PackageSetError, OSError, ValueError, subprocess.CalledProcessError) as exc:
print(f"error: {exc}")