#!/usr/bin/env python3 """ Merge Nova (Polkadot ecosystem) + Pezkuwi overlay. Sources: A: nova-base/ -> Polkadot ecosystem (98+ chains, XCM) - git submodule B: pezkuwi-overlay/ -> Pezkuwi ecosystem (4 chains, XCM) - we maintain Output: chains/ -> Merged chains (Pezkuwi first, then Nova) xcm/ -> Merged XCM (Nova base + Pezkuwi entries added to each section) icons/ -> Merged icons (Pezkuwi overrides Nova) Rules: - NOTHING gets deleted - Pezkuwi entries are ADDED to Nova's base - Pezkuwi chains appear first in the list """ import json import shutil from pathlib import Path ROOT = Path(__file__).parent.parent NOVA_BASE = ROOT / "nova-base" PEZKUWI_OVERLAY = ROOT / "pezkuwi-overlay" OUTPUT_CHAINS = ROOT / "chains" OUTPUT_XCM = ROOT / "xcm" def load_json(path: Path) -> dict | list: with open(path, 'r', encoding='utf-8') as f: return json.load(f) def save_json(path: Path, data: dict | list): path.parent.mkdir(parents=True, exist_ok=True) with open(path, 'w', encoding='utf-8') as f: json.dump(data, f, indent=2, ensure_ascii=False) f.write('\n') def merge_chains(nova_chains: list, pezkuwi_chains: list) -> list: """Pezkuwi chains first, then Nova chains (no duplicates).""" pezkuwi_ids = {c['chainId'] for c in pezkuwi_chains} nova_filtered = [c for c in nova_chains if c['chainId'] not in pezkuwi_ids] return pezkuwi_chains + nova_filtered def merge_xcm(nova_xcm: dict, pezkuwi_xcm: dict) -> dict: """ Merge XCM: Nova base + Pezkuwi entries added to EACH section. Sections: assetsLocation, instructions, networkDeliveryFee, networkBaseWeight, chains """ merged = {} # 1. assetsLocation - dict, Pezkuwi overrides/adds merged['assetsLocation'] = { **nova_xcm.get('assetsLocation', {}), **pezkuwi_xcm.get('assetsLocation', {}) } # 2. instructions - dict, keep Nova's (Pezkuwi uses same instruction types) if 'instructions' in nova_xcm: merged['instructions'] = nova_xcm['instructions'] # 3. networkDeliveryFee - dict keyed by chainId, Pezkuwi adds entries merged['networkDeliveryFee'] = { **nova_xcm.get('networkDeliveryFee', {}), **pezkuwi_xcm.get('networkDeliveryFee', {}) } # 4. networkBaseWeight - dict keyed by chainId, Pezkuwi adds entries merged['networkBaseWeight'] = { **nova_xcm.get('networkBaseWeight', {}), **pezkuwi_xcm.get('networkBaseWeight', {}) } # 5. chains - list, Pezkuwi first then Nova (no duplicates) pezkuwi_chain_ids = {c['chainId'] for c in pezkuwi_xcm.get('chains', [])} nova_chains_filtered = [ c for c in nova_xcm.get('chains', []) if c['chainId'] not in pezkuwi_chain_ids ] merged['chains'] = pezkuwi_xcm.get('chains', []) + nova_chains_filtered return merged def sync_chains(): print("Syncing chains...") pezkuwi_chains_file = PEZKUWI_OVERLAY / "chains" / "pezkuwi-chains.json" pezkuwi_chains = load_json(pezkuwi_chains_file) if pezkuwi_chains_file.exists() else [] print(f" Pezkuwi: {len(pezkuwi_chains)} chains") for version_dir in sorted(NOVA_BASE.glob("chains/v*")): version = version_dir.name output_dir = OUTPUT_CHAINS / version # chains.json nova_file = version_dir / "chains.json" if nova_file.exists(): nova_chains = load_json(nova_file) merged = merge_chains(nova_chains, pezkuwi_chains) save_json(output_dir / "chains.json", merged) print(f" {version}/chains.json: {len(pezkuwi_chains)} + {len(nova_chains)} = {len(merged)}") # chains_dev.json nova_dev = version_dir / "chains_dev.json" if nova_dev.exists(): nova_chains = load_json(nova_dev) merged = merge_chains(nova_chains, pezkuwi_chains) save_json(output_dir / "chains_dev.json", merged) # android/chains.json android_dir = output_dir / "android" if nova_file.exists(): save_json(android_dir / "chains.json", merge_chains(load_json(nova_file), pezkuwi_chains)) # preConfigured (copy from Nova) nova_preconfig = version_dir / "preConfigured" if nova_preconfig.exists(): output_preconfig = output_dir / "preConfigured" if output_preconfig.exists(): shutil.rmtree(output_preconfig) shutil.copytree(nova_preconfig, output_preconfig) def sync_xcm(): print("\nSyncing XCM...") # Load Pezkuwi XCM overlays pezkuwi_xcm_file = PEZKUWI_OVERLAY / "xcm" / "pezkuwi-xcm.json" pezkuwi_xcm_dynamic_file = PEZKUWI_OVERLAY / "xcm" / "pezkuwi-xcm-dynamic.json" pezkuwi_xcm = load_json(pezkuwi_xcm_file) if pezkuwi_xcm_file.exists() else {} pezkuwi_xcm_dynamic = load_json(pezkuwi_xcm_dynamic_file) if pezkuwi_xcm_dynamic_file.exists() else {} print(f" Pezkuwi XCM: {len(pezkuwi_xcm.get('chains', []))} chains") print(f" Pezkuwi XCM dynamic: {len(pezkuwi_xcm_dynamic.get('chains', []))} chains") for version_dir in sorted(NOVA_BASE.glob("xcm/v*")): version = version_dir.name output_dir = OUTPUT_XCM / version output_dir.mkdir(parents=True, exist_ok=True) for nova_file in version_dir.glob("*.json"): filename = nova_file.name nova_data = load_json(nova_file) # Choose overlay based on filename if 'dynamic' in filename: overlay = pezkuwi_xcm_dynamic else: overlay = pezkuwi_xcm merged = merge_xcm(nova_data, overlay) save_json(output_dir / filename, merged) print(f" {version}/{filename}: {len(merged.get('chains', []))} chains") # Root XCM files for xcm_file in NOVA_BASE.glob("xcm/*.json"): shutil.copy(xcm_file, OUTPUT_XCM / xcm_file.name) def sync_icons(): print("\nSyncing icons...") nova_icons = NOVA_BASE / "icons" pezkuwi_icons = PEZKUWI_OVERLAY / "icons" output_icons = ROOT / "icons" # Copy Nova icons (don't overwrite existing) if nova_icons.exists(): for icon_dir in nova_icons.iterdir(): if icon_dir.is_dir(): output_dir = output_icons / icon_dir.name output_dir.mkdir(parents=True, exist_ok=True) for icon_file in icon_dir.rglob("*"): if icon_file.is_file(): rel = icon_file.relative_to(icon_dir) target = output_dir / rel if not target.exists(): target.parent.mkdir(parents=True, exist_ok=True) shutil.copy(icon_file, target) # Copy Pezkuwi icons (override Nova) if pezkuwi_icons.exists(): for icon_file in pezkuwi_icons.rglob("*"): if icon_file.is_file(): rel = icon_file.relative_to(pezkuwi_icons) target = output_icons / rel target.parent.mkdir(parents=True, exist_ok=True) shutil.copy(icon_file, target) print(f" Pezkuwi: {rel}") def main(): print("=" * 60) print("Nova + Pezkuwi Merge") print("=" * 60) print(f"Nova (A): {NOVA_BASE}") print(f"Pezkuwi (B): {PEZKUWI_OVERLAY}") print() if not NOVA_BASE.exists(): print("ERROR: nova-base not found!") print("Run: git submodule update --init --recursive") return 1 sync_chains() sync_xcm() sync_icons() print("\n" + "=" * 60) print("Done!") print("=" * 60) return 0 if __name__ == "__main__": exit(main())