import { parseDocument, stringify } from "yaml"; export class CustomProxyValidationError extends Error {} function isRecord(value: unknown): value is Record { return Boolean(value) && typeof value === "object" && !Array.isArray(value); } /** Accept a single proxy, a one-item list, or the legacy `proxies:` wrapper. */ export function parseCustomProxyYaml( source: string, ): Record & { name: string; type: string } { if (!source.trim()) throw new CustomProxyValidationError("请填写节点 YAML。"); if (source.length > 128 * 1024) throw new CustomProxyValidationError("单个节点 YAML 不能超过 128 KiB。"); const doc = parseDocument(source, { prettyErrors: false, uniqueKeys: true }); if (doc.errors.length || doc.warnings.length) { // Parser messages can contain credentials from the YAML; do not echo them. throw new CustomProxyValidationError( "YAML 格式错误,请检查缩进、引号和重复字段。", ); } let value: unknown; try { value = doc.toJS({ maxAliasCount: 100 }); } catch { throw new CustomProxyValidationError( "YAML 引用无效或过多,请使用独立的节点配置。", ); } if (isRecord(value) && "proxies" in value) value = value.proxies; if (Array.isArray(value)) { if (value.length !== 1) throw new CustomProxyValidationError( "每条节点记录只能包含一个 proxy,请将多个节点分别添加。", ); value = value[0]; } if (!isRecord(value)) throw new CustomProxyValidationError( "请填写一个 Clash proxy 对象或单节点列表。", ); if (typeof value.name !== "string" || !value.name.trim()) { throw new CustomProxyValidationError( "节点 YAML 必须包含非空的 name 字段(字符串)。", ); } if (typeof value.type !== "string" || !value.type.trim()) { throw new CustomProxyValidationError( "节点 YAML 必须包含非空的 type 字段,例如 anytls。", ); } return value as Record & { name: string; type: string }; } /** Re-serialize only for output; stored source and comments remain untouched. */ export function renderCustomProxyYaml(source: string) { return stringify([parseCustomProxyYaml(source)], { lineWidth: 0 }).trimEnd(); } export function extractProxyNameFromYaml(source: string) { try { return parseCustomProxyYaml(source).name; } catch { return null; } }