83 lines
2.9 KiB
Python
83 lines
2.9 KiB
Python
#!/usr/bin/env python3
|
|
"""Sync byte-identical workflows from workflows-src/ to .gitea/ + .github/ (P8, REQ-172).
|
|
|
|
Three workflow pairs are byte-identical Gitea + GitHub mirrors:
|
|
ci.yml, deploy.yml, modules-lifecycle.yml.
|
|
|
|
This generator reads the single source from ``workflows-src/<name>`` and
|
|
writes byte-identical copies to both ``.gitea/workflows/<name>`` and
|
|
``.github/workflows/<name>``. Use ``--check`` to verify the committed
|
|
files match the generated output (CI gate); use ``--write`` to regenerate
|
|
the committed files from the sources.
|
|
|
|
The 4 GitHub-only workflows (platform-test.yml, primitives-plan.yml,
|
|
patterns-plan.yml, release.yml) have no Gitea mirror (act_runner feature
|
|
gaps) and are NOT touched by this generator.
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
import filecmp
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
ROOT = Path(__file__).resolve().parent.parent
|
|
SRC_DIR = ROOT / "workflows-src"
|
|
GITEA_DIR = ROOT / ".gitea" / "workflows"
|
|
GITHUB_DIR = ROOT / ".github" / "workflows"
|
|
|
|
PAIRS = ["ci.yml", "deploy.yml", "modules-lifecycle.yml"]
|
|
|
|
|
|
def _read_source(name: str) -> str:
|
|
src = SRC_DIR / name
|
|
if not src.is_file():
|
|
raise FileNotFoundError(f"source {src} missing")
|
|
return src.read_text()
|
|
|
|
|
|
def check() -> int:
|
|
"""Verify committed files match the sources. Exit 0 if clean, 1 if drift."""
|
|
drift = []
|
|
for name in PAIRS:
|
|
content = _read_source(name)
|
|
for dest_dir in (GITEA_DIR, GITHUB_DIR):
|
|
dest = dest_dir / name
|
|
if not dest.is_file():
|
|
drift.append(f"{dest} MISSING (expected from workflows-src/{name})")
|
|
continue
|
|
if dest.read_text() != content:
|
|
drift.append(f"{dest} DRIFTED from workflows-src/{name}")
|
|
if drift:
|
|
for d in drift:
|
|
print(f"DRIFT: {d}", file=sys.stderr)
|
|
print("\nRun: python3 scripts/sync_workflows.py --write", file=sys.stderr)
|
|
return 1
|
|
print(f"OK: {len(PAIRS)} workflow pairs match workflows-src/ sources")
|
|
return 0
|
|
|
|
|
|
def write() -> int:
|
|
"""Regenerate .gitea/ + .github/ from workflows-src/ sources."""
|
|
for name in PAIRS:
|
|
content = _read_source(name)
|
|
for dest_dir in (GITEA_DIR, GITHUB_DIR):
|
|
dest_dir.mkdir(parents=True, exist_ok=True)
|
|
(dest_dir / name).write_text(content)
|
|
print(f"wrote: .gitea/workflows/{name} + .github/workflows/{name}")
|
|
return 0
|
|
|
|
|
|
def main(argv: list[str] | None = None) -> int:
|
|
parser = argparse.ArgumentParser(description="Sync byte-identical workflow pairs.")
|
|
group = parser.add_mutually_exclusive_group(required=True)
|
|
group.add_argument("--check", action="store_true", help="verify committed files match sources (CI gate)")
|
|
group.add_argument("--write", action="store_true", help="regenerate committed files from sources")
|
|
args = parser.parse_args(argv)
|
|
if args.check:
|
|
return check()
|
|
return write()
|
|
|
|
|
|
if __name__ == "__main__":
|
|
sys.exit(main()) |