Repository Public
1#!/usr/bin/env python32"""Resolve the predictable conflicts produced when two chain-family branches both extend the registries.3Rule: keep both sides. Run after `git merge <branch>` reports conflicts, then `pnpm install`, typecheck and test."""4import os, re, subprocess5R = re.compile(r"<<<<<<< HEAD\n(.*?)=======\n(.*?)>>>>>>> [^\n]+\n", re.S)6ENTRY = re.compile(r"([A-Za-z_][A-Za-z0-9_]*): ('[^']*'|[A-Za-z0-9_]+)")7def union_entries(a, b):8 """Two versions of `k: v, k: v, …` lines: HEAD's text plus the entries only the other side has, as extra lines."""9 have = {k for k, _ in ENTRY.findall(a)}10 extra = [ln for ln in b.splitlines() if any(k not in have for k, _ in ENTRY.findall(ln.split('//')[0]))]11 out = a12 for ln in extra:13 new = [(k, v) for k, v in ENTRY.findall(ln.split('//')[0]) if k not in have]14 comment = (' //' + ln.split('//', 1)[1]) if '//' in ln and len(new) == len(ENTRY.findall(ln.split('//')[0])) else ''15 out += ' ' + ', '.join(f"{k}: {v}" for k, v in new) + ',' + comment + '\n'16 return out17def fix(path, fn):18 s = open(path).read()19 if '<<<<<<< HEAD' not in s: return20 open(path, 'w').write(R.sub(fn, s)); print('resolved', path)21def networks(m):22 a, b = m.group(1), m.group(2)23 if re.search(r"^\s+id: '", a, re.M) and re.search(r"^\s+id: '", b, re.M): return a + " },\n" + b # two network entries24 return a + ''.join(ln + '\n' for ln in b.splitlines() if ln not in a.splitlines()) # comment lines25fix('packages/core/src/networks.ts', networks)26fix('packages/core/src/policy.ts', lambda m: union_entries(m.group(1), m.group(2)))27fix('services/api/src/server.ts', lambda m: union_entries(m.group(1), m.group(2)))28fix('apps/web/src/i18n.ts', lambda m: m.group(1) + m.group(2))29fix('scripts/smoke-chains.mts', lambda m: m.group(1))30fix('package.json', lambda m: ''.join(sorted(set((m.group(1) + m.group(2)).splitlines(True))))) # devDependencies: keep both, sorted31# registries that are pure functions of the files present: regenerate32plug = sorted(f[:-3] for f in os.listdir('packages/core/src/plugins') if f.endswith('.ts') and f != 'index.ts')33open('packages/core/src/plugins/index.ts', 'w').write("// Chain family plugins. Importing this module registers every plugin with the registry in families.ts.\nimport { registerFamily } from '../families.ts';\n"34 + ''.join(f"import {{ {p}Plugin }} from './{p}.ts';\n" for p in plug) + f"\nfor (const p of [{', '.join(p + 'Plugin' for p in plug)}]) registerFamily(p);\n\n" + ''.join(f"export * from './{p}.ts';\n" for p in plug))35p = 'services/api/src/chains/index.ts'; s = open(p).read()36fams = sorted(set(plug))37s = R.sub(lambda m: m.group(1), s)38s = re.sub(r"(import \{ [a-z]+Adapter \} from '\./[a-z]+\.ts';\n)+", ''.join(f"import {{ {f}Adapter }} from './{f}.ts';\n" for f in sorted(['btc', 'evm', 'sol'] + fams)), s, count=1)39s = re.sub(r"export const ADAPTER_FACTORIES: Record<string, \(n: NetworkDef\) => Adapter> = \{[^}]*\};",40 "export const ADAPTER_FACTORIES: Record<string, (n: NetworkDef) => Adapter> = { " + ', '.join(f"{f}: {f}Adapter" for f in ['evm', 'btc', 'sol'] + fams) + " };", s)41open(p, 'w').write(s)42# swap provider registry: regenerate from the adapter files present (each exports create<Name>Provider)43pdir = 'packages/swap/src/providers'44if os.path.isdir(pdir):45 facs = []46 for f in sorted(os.listdir(pdir)):47 if not f.endswith('.ts') or f == 'index.ts': continue48 names = re.findall(r"export (?:function|const) (create[A-Za-z0-9]+Provider)\b", open(os.path.join(pdir, f)).read())49 # the factory named after the file (create<File>Provider); helper factories exported by the same file are not providers50 own = [x for x in names if x.lower() == 'create' + f[:-3].replace('-', '').lower() + 'provider']51 if own: facs.append((f, own[0]))52 open(os.path.join(pdir, 'index.ts'), 'w').write("// Provider registry (regenerated by scripts/merge-registries.py). One file per provider; each exports a factory returning a SwapProvider.\n// Odos is deliberately absent: its API was permanently shut down on 2026-07-30 (docs/swap/providers/odos.md).\nimport type { SwapProvider } from '../types.ts';\n"53 + ''.join(f"import {{ {fn} }} from './{f}';\n" for f, fn in facs)54 + "\nexport interface ProviderEnv { [key: string]: string | undefined }\n/** Every real provider adapter, in a fixed order. A provider that needs a key reports `configured: false` without one. */\nexport function createProviders(env: ProviderEnv = {}): SwapProvider[] {\n return ["55 + ', '.join(f"{fn}(env)" for _, fn in facs) + "];\n}\n")56subprocess.run(['git', 'checkout', '--theirs', 'pnpm-lock.yaml'], capture_output=True)57left = subprocess.run("git diff --name-only --diff-filter=U", shell=True, capture_output=True, text=True).stdout.split()58bad = [f for f in left if f != 'pnpm-lock.yaml' and '<<<<<<<' in open(f).read()]59print('still conflicted:', bad or 'none')60