Add metin-release CLI with argparse dispatcher, result envelope and error hierarchy, and the full Phase 1 release subcommand set: release inspect - scan source root release build-manifest - wraps make-manifest.py release sign - wraps sign-manifest.py, enforces mode 600 release diff-remote - HEAD each blob hash against a base URL release upload-blobs - rsync release dir minus manifest release promote - rsync manifest.json + signature release verify-public - GET + Ed25519 verify, optional blob sampling release publish - composite of the above with per-stage timings Respects --json / --verbose / --quiet. Exit codes follow the plan (1 validation, 2 remote, 3 integrity, 4 reserved for ERP).
54 lines
1.5 KiB
Python
54 lines
1.5 KiB
Python
"""Result envelope and JSON/human output writer."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
import sys
|
|
from dataclasses import dataclass, field
|
|
from typing import Any
|
|
|
|
|
|
@dataclass
|
|
class Result:
|
|
"""Structured result for a CLI command."""
|
|
|
|
command: str
|
|
ok: bool = True
|
|
status: str = "ok"
|
|
version: str | None = None
|
|
data: dict[str, Any] = field(default_factory=dict)
|
|
error_code: str | None = None
|
|
error_message: str | None = None
|
|
|
|
def to_envelope(self) -> dict[str, Any]:
|
|
env: dict[str, Any] = {
|
|
"ok": self.ok,
|
|
"command": self.command,
|
|
}
|
|
if self.version is not None:
|
|
env["version"] = self.version
|
|
env["status"] = self.status
|
|
if self.ok:
|
|
for k, v in self.data.items():
|
|
env[k] = v
|
|
else:
|
|
env["error"] = {
|
|
"code": self.error_code or "error",
|
|
"message": self.error_message or "",
|
|
}
|
|
return env
|
|
|
|
|
|
def write_result(result: Result, *, json_mode: bool, human_summary: str | None = None) -> None:
|
|
"""Emit the result.
|
|
|
|
When json_mode is True: only JSON on stdout.
|
|
When json_mode is False: human summary on stderr, JSON on stdout.
|
|
"""
|
|
envelope = result.to_envelope()
|
|
if not json_mode and human_summary:
|
|
print(human_summary, file=sys.stderr)
|
|
json.dump(envelope, sys.stdout, indent=2, sort_keys=False)
|
|
sys.stdout.write("\n")
|
|
sys.stdout.flush()
|