Skip to content

Commit bd9880d

Browse files
committed
feat: add GitHub Actions workflows for publishing manifest and updating model registry
0 parents  commit bd9880d

5 files changed

Lines changed: 233 additions & 0 deletions

File tree

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
name: Publish manifest to GitHub Pages
2+
3+
on:
4+
push:
5+
branches:
6+
- main
7+
workflow_dispatch:
8+
9+
jobs:
10+
publish:
11+
runs-on: ubuntu-latest
12+
steps:
13+
- name: Checkout repository
14+
uses: actions/checkout@v4
15+
with:
16+
fetch-depth: 1
17+
18+
- name: Setup Pages
19+
uses: actions/configure-pages@v4
20+
21+
- name: Copy manifest to pages directory
22+
run: |
23+
mkdir -p public
24+
cp magic/manifest.json public/manifest.json
25+
26+
- name: Upload artifact
27+
uses: actions/upload-pages-artifact@v2
28+
with:
29+
path: public
30+
31+
- name: Deploy to GitHub Pages
32+
id: deployment
33+
uses: actions/deploy-pages@v3

.github/workflows/registry.yml

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
name: Update model registry
2+
3+
on:
4+
push:
5+
paths:
6+
- "models/*.magic"
7+
- "scripts/update_registry.py"
8+
- ".github/workflows/registry.yml"
9+
branches:
10+
- main
11+
12+
permissions:
13+
contents: write
14+
15+
jobs:
16+
build-registry:
17+
runs-on: ubuntu-latest
18+
env:
19+
DEFAULT_BRANCH: main
20+
steps:
21+
- name: Checkout
22+
uses: actions/checkout@v4
23+
with:
24+
persist-credentials: true
25+
26+
- name: Set up Python
27+
uses: actions/setup-python@v5
28+
with:
29+
python-version: "3.11"
30+
31+
- name: Install deps (none needed)
32+
run: python -V
33+
34+
- name: Update registry.json
35+
run: python scripts/update_registry.py
36+
37+
- name: Commit registry.json if changed
38+
run: |
39+
if [[ -n "$(git status --porcelain data/registry.json)" ]]; then
40+
git config user.name "github-actions[bot]"
41+
git config user.email "github-actions@github.com"
42+
git add data/registry.json
43+
git commit -m "chore(registry): update after new .magic"
44+
git push
45+
else
46+
echo "No changes to commit."
47+
fi

magic/manifest.json

Whitespace-only changes.

magic/upload/Luma_081539D.magic

342 Bytes
Binary file not shown.

scripts/update_registry.py

Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
import os, re, json, zipfile, hashlib, sys
2+
from datetime import datetime
3+
from pathlib import Path
4+
5+
REPO_OWNER = os.environ.get("GITHUB_REPOSITORY", "owner/repo").split("/")[0]
6+
REPO_NAME = os.environ.get("GITHUB_REPOSITORY", "owner/repo").split("/")[1]
7+
DEFAULT_BRANCH = os.environ.get("DEFAULT_BRANCH", "main")
8+
9+
DIST_DIR = Path("magic/upload")
10+
REGISTRY_PATH = Path("magic/manifest.json")
11+
12+
BUILD_MAP = {
13+
"A": "stable",
14+
"B": "rc",
15+
"C": "beta",
16+
"D": "alpha",
17+
"F": "debug",
18+
}
19+
20+
FILENAME_RE = re.compile(r"^(?P<name>[^_]+)_(?P<version>[^_]+)_(?P<buildid>[A-Za-z0-9]+)\.magic$")
21+
22+
def sha256_file(path: Path) -> str:
23+
h = hashlib.sha256()
24+
with open(path, "rb") as f:
25+
for chunk in iter(lambda: f.read(1024 * 1024), b""):
26+
h.update(chunk)
27+
return h.hexdigest()
28+
29+
def parse_build_type(build_id: str) -> str:
30+
if len(build_id) >= 3:
31+
letter = build_id[2].upper()
32+
return BUILD_MAP.get(letter, "unknown")
33+
return "unknown"
34+
35+
def read_model_infos_from_magic(magic_path: Path) -> dict:
36+
with zipfile.ZipFile(magic_path, "r") as z:
37+
candidate = None
38+
for name in z.namelist():
39+
if name.lower().endswith("infos.json"):
40+
candidate = name
41+
break
42+
if not candidate:
43+
raise FileNotFoundError(f"Aucun infos.json dans {magic_path.name}")
44+
45+
with z.open(candidate) as f:
46+
data = json.loads(f.read().decode("utf-8"))
47+
48+
return data
49+
50+
def load_registry() -> dict:
51+
if REGISTRY_PATH.exists():
52+
try:
53+
with open(REGISTRY_PATH, "r", encoding="utf-8") as f:
54+
content = f.read().strip()
55+
if not content:
56+
print(f"[WARN] {REGISTRY_PATH} est vide, création d'un nouveau registry")
57+
return {"models": []}
58+
return json.loads(content)
59+
except json.JSONDecodeError as e:
60+
print(f"[WARN] {REGISTRY_PATH} contient du JSON invalide: {e}")
61+
print("Création d'un nouveau registry")
62+
return {"models": []}
63+
except Exception as e:
64+
print(f"[ERREUR] Impossible de lire {REGISTRY_PATH}: {e}")
65+
return {"models": []}
66+
return {"models": []}
67+
68+
def save_registry(registry: dict):
69+
REGISTRY_PATH.parent.mkdir(parents=True, exist_ok=True)
70+
registry["models"].sort(key=lambda m: (m.get("name",""), m.get("version","")), reverse=False)
71+
with open(REGISTRY_PATH, "w", encoding="utf-8") as f:
72+
json.dump(registry, f, indent=2, ensure_ascii=False)
73+
74+
def upsert_model(registry: dict, model_entry: dict):
75+
replaced = False
76+
for i, m in enumerate(registry["models"]):
77+
if m.get("name")==model_entry.get("name") and m.get("version")==model_entry.get("version"):
78+
registry["models"][i] = model_entry
79+
replaced = True
80+
break
81+
if not replaced:
82+
registry["models"].append(model_entry)
83+
84+
def main():
85+
if not DIST_DIR.exists():
86+
print("magic/upload/ n'existe pas, rien à faire.")
87+
return 0
88+
89+
registry = load_registry()
90+
91+
magic_files = [p for p in DIST_DIR.glob("*.magic") if p.is_file()]
92+
if not magic_files:
93+
print("Aucun .magic trouvé dans magic/upload/.")
94+
return 0
95+
96+
for magic_path in magic_files:
97+
m = FILENAME_RE.match(magic_path.name)
98+
if not m:
99+
print(f"Ignore {magic_path.name} (nom inattendu)")
100+
continue
101+
102+
name = m.group("name")
103+
version = m.group("version")
104+
build_id = m.group("buildid")
105+
build_type = parse_build_type(build_id)
106+
107+
try:
108+
infos = read_model_infos_from_magic(magic_path)
109+
except Exception as e:
110+
print(f"[ERREUR] {magic_path.name}: {e}")
111+
continue
112+
113+
if infos.get("version") and infos["version"] != version:
114+
print(f"[WARN] version mismatch: filename={version} / infos.json={infos['version']}")
115+
116+
size_bytes = magic_path.stat().st_size
117+
sha = sha256_file(magic_path)
118+
119+
# URL de téléchargement (GitHub par défaut, ou local pour le dev)
120+
if os.environ.get("DEV_SERVER_IP"):
121+
dev_ip = os.environ.get("DEV_SERVER_IP")
122+
dev_port = os.environ.get("DEV_SERVER_PORT", "8000")
123+
download_url = f"http://{dev_ip}:{dev_port}/magic/upload/{magic_path.name}"
124+
else:
125+
download_url = f"https://raw.githubusercontent.com/{REPO_OWNER}/{REPO_NAME}/{DEFAULT_BRANCH}/magic/upload/{magic_path.name}"
126+
127+
compat = infos.get("compatible_versions") or infos.get("compat") or []
128+
if isinstance(compat, str):
129+
compat = [compat]
130+
131+
entry = {
132+
"name": infos.get("name", name),
133+
"version": version,
134+
"author": infos.get("author", "unknown"),
135+
"date_created": infos.get("date_created") or datetime.utcnow().strftime("%Y-%m-%d"),
136+
"parameters": infos.get("parameters", {}),
137+
"compatible_versions": compat,
138+
"size_bytes": size_bytes,
139+
"sha256": sha,
140+
"download_url": download_url,
141+
"build_id": build_id,
142+
"build_type": build_type,
143+
}
144+
145+
upsert_model(registry, entry)
146+
print(f"✅ Ajout/MàJ: {entry['name']} {entry['version']} ({build_type})")
147+
148+
save_registry(registry)
149+
print(f"💾 Écrit {REGISTRY_PATH}")
150+
return 0
151+
152+
if __name__ == "__main__":
153+
sys.exit(main())

0 commit comments

Comments
 (0)