feat: sync extensions.description from manifest.json

Manifest is now the source of truth for the catalog label shown in the
admin UI. build_and_publish updates description on every release; the
new _sync_description_from_manifest helper is also called from
sync_extension_registry for backfill / drift correction (runs every 6h
via the auto-add-webhook workflow, or on manual /sync-registry).

This replaces the hardcoded HEBREW_LABELS map in KlearBranding's
admin/index.js — new extensions inherit their label from their manifest
without code changes.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-25 17:47:21 +00:00
parent 50c408c4c4
commit 18d54e1403
+32
View File
@@ -166,6 +166,12 @@ def sync_extension_registry():
) sub WHERE e.id = sub.extension_id AND (e.latest_version IS NULL OR e.latest_version != sub.max_ver)""", ) sub WHERE e.id = sub.extension_id AND (e.latest_version IS NULL OR e.latest_version != sub.max_ver)""",
fetch=None) fetch=None)
# Sync description from each extension's manifest.json (main branch) — manifest is source of truth for the catalog label
description_updates = []
for name in gitea_extensions:
if _sync_description_from_manifest(name):
description_updates.append(name)
# Reload MODULES from DB # Reload MODULES from DB
_load_modules_from_db() _load_modules_from_db()
@@ -173,9 +179,31 @@ def sync_extension_registry():
'status': 'ok', 'status': 'ok',
'gitea_extensions': len(gitea_extensions), 'gitea_extensions': len(gitea_extensions),
'added': added, 'reactivated': reactivated, 'deactivated': deactivated, 'added': added, 'reactivated': reactivated, 'deactivated': deactivated,
'description_updates': description_updates,
'active_modules': sorted(MODULES.keys()) 'active_modules': sorted(MODULES.keys())
} }
def _sync_description_from_manifest(module):
"""Fetch manifest.json from the extension's Gitea main branch and update extensions.description.
Returns True if the description was changed."""
resp = gitea_api('GET', f'/repos/{GITEA_ORG}/{module}/contents/manifest.json')
if not isinstance(resp, dict) or not resp.get('content'):
return False
try:
manifest = json.loads(base64.b64decode(resp['content']))
except Exception:
return False
desc = (manifest.get('description') or '').strip()
if not desc:
return False
current = query("SELECT description FROM extensions WHERE name = %s", (module,), fetch='one')
if current and (current.get('description') or '').strip() == desc:
return False
query("UPDATE extensions SET description = %s, updated_at = NOW() WHERE name = %s",
(desc, module), fetch=None)
return True
SSL_CTX = ssl.create_default_context() SSL_CTX = ssl.create_default_context()
SSL_CTX.check_hostname = False SSL_CTX.check_hostname = False
SSL_CTX.verify_mode = ssl.CERT_NONE SSL_CTX.verify_mode = ssl.CERT_NONE
@@ -376,6 +404,10 @@ def build_and_publish(module, version):
cur.execute("""UPDATE extensions SET latest_version = %s, updated_at = NOW() cur.execute("""UPDATE extensions SET latest_version = %s, updated_at = NOW()
WHERE name = %s AND (latest_version IS NULL OR string_to_array(latest_version, '.')::int[] < string_to_array(%s, '.')::int[])""", WHERE name = %s AND (latest_version IS NULL OR string_to_array(latest_version, '.')::int[] < string_to_array(%s, '.')::int[])""",
(version, module, version)) (version, module, version))
manifest_desc = (manifest.get('description') or '').strip()
if manifest_desc:
cur.execute("UPDATE extensions SET description = %s, updated_at = NOW() WHERE name = %s",
(manifest_desc, module))
conn.commit() conn.commit()
cur.close() cur.close()
conn.close() conn.close()