import { execFileSync } from 'node:child_process' import fs from 'node:fs' import path from 'node:path' /** * Regenerates src/services/contract.ts from the backend wire contract. * Source of truth: `cargo run -p clash-verge --bin frontend-contract` inside * src-tauri, which serializes the same enum variants and event payloads the * app emits at runtime. CI reruns this and fails on a non-empty diff. */ const ROOT = path.resolve(import.meta.dirname, '..') const OUT = path.join(ROOT, 'src/services/contract.ts') const contract = JSON.parse( execFileSync( 'cargo', ['run', '-p', 'clash-verge', '--bin', 'frontend-contract', '-q'], { cwd: path.join(ROOT, 'src-tauri'), encoding: 'utf8', maxBuffer: 16 * 1024 * 1024, }, ), ) if (contract.version !== 1) { throw new Error(`unsupported wire contract version: ${contract.version}`) } /** TS type per event, derived from the serialized sample payload's JSON shape. */ const payloadType = (name, sample) => { if (sample === null) return 'null' if (typeof sample === 'string') return 'string' if (Array.isArray(sample)) { if (name === 'verge://notice-message') return '[NoticeStatus, string]' throw new Error(`unexpected array payload for ${name}`) } if (typeof sample === 'object') { const keys = Object.keys(sample) if (keys.length === 1 && keys[0] === 'uid') return '{ uid: string }' if (name === 'verge://run-state-changed') return 'RunState' throw new Error(`unexpected object payload for ${name}: ${keys.join(',')}`) } throw new Error(`unexpected payload for ${name}`) } const needsRunState = contract.events.some( (e) => e.name === 'verge://run-state-changed', ) if (!needsRunState) throw new Error('run-state-changed event missing from contract') const lines = [] lines.push( '// @generated by scripts/gen-contract.mjs from the backend wire contract. DO NOT EDIT.', ) lines.push('// Regen: pnpm gen:contract') lines.push('') lines.push("import type { RunState } from './cmds'") lines.push('') lines.push( '/** Wire status strings from NoticeStatus; keep in sync via `pnpm gen:contract`. */', ) lines.push('export type NoticeStatus =') for (const status of contract.noticeStatuses) { lines.push(` | ${JSON.stringify(status)}`) } lines.push('') lines.push('/** Event name → payload type for every backend-emitted event. */') lines.push('export interface VergeEventPayloads {') for (const event of contract.events) { const key = JSON.stringify(event.name) lines.push(` ${key}: ${payloadType(event.name, event.sample)}`) } lines.push('}') lines.push('') const content = `${lines.join('\n')}\n` fs.writeFileSync(OUT, content) // Match the repo's biome style so CI parity (`git diff --exit-code`) is stable. execFileSync( process.platform === 'win32' ? 'pnpm.cmd' : 'pnpm', ['exec', 'biome', 'format', '--write', path.relative(ROOT, OUT)], { cwd: ROOT, stdio: 'ignore' }, ) console.log( `[gen-contract] wrote ${path.relative(ROOT, OUT)}: ${contract.events.length} events, ${contract.noticeStatuses.length} statuses`, )