"""Phase 4 — publish the skill library into the running Gitea instance. Creates (idempotent, safe to re-run): - orgs: skills-core (public), skills-community (public), tenant-acme (private) - one repo per package in skills-core, pushed via git (real commits) - skills-community/recruitment-consultant (community edition, PR target) - skills-core/marketplace with marketplace.json + README (logo, mermaid) The tenant fork/PR demo lives in p4b_tenant_demo.py. Gitea runs on MSSQL behind Caddy; pushes are serialized with a small pause (single-core SQL Express — be gentle). """ import json import os import shutil import subprocess import sys import tempfile import time import requests from dotenv import load_dotenv BASE = os.path.join(os.path.dirname(__file__), "..") load_dotenv(os.path.join(BASE, ".env")) GITEA_URL = os.environ["GITEA_URL"].rstrip("/") TOKEN = os.environ["GITEA_TOKEN"] API = f"{GITEA_URL}/api/v1" HDR = {"Authorization": f"token {TOKEN}"} SKILLS_DIR = os.path.join(BASE, "skills") PUSH_PAUSE_S = 1.5 def api(method, path, ok=(200, 201, 204), **kw): r = requests.request(method, f"{API}{path}", headers=HDR, timeout=120, **kw) if r.status_code not in ok: raise RuntimeError(f"{method} {path} -> {r.status_code}: {r.text[:300]}") return r def ensure_org(name, visibility): r = requests.get(f"{API}/orgs/{name}", headers=HDR, timeout=60) if r.status_code == 200: return api("POST", "/orgs", json={"username": name, "visibility": visibility, "full_name": name, "description": f"skillfactor — {name}"}) print(f"org created: {name} ({visibility})") def ensure_repo(org, name, description=""): r = requests.get(f"{API}/repos/{org}/{name}", headers=HDR, timeout=60) if r.status_code == 200: return False api("POST", f"/orgs/{org}/repos", json={"name": name, "description": description[:255], "private": False, "default_branch": "main", "auto_init": False}) print(f"repo created: {org}/{name}") return True def push_dir(org, repo, src_dir, message): """Push src_dir content as single commit to main (force: package is source of truth).""" push_url = GITEA_URL.replace("://", f"://gitadmin:{TOKEN}@") + f"/{org}/{repo}.git" tmp = tempfile.mkdtemp(prefix="sfpush_") try: shutil.copytree(src_dir, tmp, dirs_exist_ok=True) def g(*args): subprocess.run(["git", *args], cwd=tmp, check=True, capture_output=True, text=True) g("init", "-b", "main") g("config", "user.name", "skillfactor-pipeline") g("config", "user.email", "pipeline@noreply.zeiterfassung.cloud") g("add", "-A") g("commit", "-m", message) g("push", "--force", push_url, "main") finally: shutil.rmtree(tmp, ignore_errors=True) def build_marketplace_dir(slugs): tmp = tempfile.mkdtemp(prefix="sfmarket_") # globale Provenienz aus den Manifests aggregieren (fuer das README-Pie) prov_labels = {"esco": "ESCO (occupations & competences)", "onet": "O*NET (tasks & tools)", "jobads": "Job boards (market evidence)", "wiki_ai": "Wikipedia & AI expert curation"} prov = {k: 0 for k in prov_labels} for slug in slugs: m = json.load(open(os.path.join(SKILLS_DIR, slug, "manifest.json"), encoding="utf-8")) for k in prov: prov[k] += (m.get("provenance", {}).get("items", {}) or {}).get(k, 0) prov_rows = "\n".join(f' "{prov_labels[k]}" : {v}' for k, v in prov.items() if v) index = [] for slug in slugs: manifest = json.load(open(os.path.join(SKILLS_DIR, slug, "manifest.json"), encoding="utf-8")) index.append({ "name": manifest["name"], "title": manifest["title"], "slug": slug, "esco_uri": manifest["ids"]["esco_uri"], "onet_soc": manifest["ids"]["onet_soc"], "version": manifest["version"], "layer": manifest["layer"], "repo": f"{GITEA_URL}/skills-core/{slug}", }) json.dump(index, open(os.path.join(tmp, "marketplace.json"), "w", encoding="utf-8"), indent=2) os.makedirs(os.path.join(tmp, "assets"), exist_ok=True) for logo in ("skillfactor_logo_red.png", "skillfactor_mark_red.png"): shutil.copy(os.path.join(BASE, "assets", logo), os.path.join(tmp, "assets", logo)) readme = f"""
