mirror of
https://github.com/clash-verge-rev/clash-verge-rev.git
synced 2026-10-25 05:02:07 +08:00
Adds a zero-dependency `frontend-contract` bin that serializes the real NoticeStatus variants and FrontendEvent payloads, scripts/gen-contract.mjs which emits the committed src/services/contract.ts, and a CI job that regenerates it and fails on a non-empty diff. events.ts now derives its event map from the generated contract instead of hand-typed shapes.
92 lines
3.0 KiB
JavaScript
92 lines
3.0 KiB
JavaScript
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`,
|
|
)
|