Spaces:
Paused
Paused
| import JSON5 from "json5"; | |
| import type { | |
| OpenClawHookMetadata, | |
| HookEntry, | |
| HookInstallSpec, | |
| HookInvocationPolicy, | |
| ParsedHookFrontmatter, | |
| } from "./types.js"; | |
| import { LEGACY_MANIFEST_KEYS, MANIFEST_KEY } from "../compat/legacy-names.js"; | |
| import { parseFrontmatterBlock } from "../markdown/frontmatter.js"; | |
| import { parseBooleanValue } from "../utils/boolean.js"; | |
| export function parseFrontmatter(content: string): ParsedHookFrontmatter { | |
| return parseFrontmatterBlock(content); | |
| } | |
| function normalizeStringList(input: unknown): string[] { | |
| if (!input) { | |
| return []; | |
| } | |
| if (Array.isArray(input)) { | |
| return input.map((value) => String(value).trim()).filter(Boolean); | |
| } | |
| if (typeof input === "string") { | |
| return input | |
| .split(",") | |
| .map((value) => value.trim()) | |
| .filter(Boolean); | |
| } | |
| return []; | |
| } | |
| function parseInstallSpec(input: unknown): HookInstallSpec | undefined { | |
| if (!input || typeof input !== "object") { | |
| return undefined; | |
| } | |
| const raw = input as Record<string, unknown>; | |
| const kindRaw = | |
| typeof raw.kind === "string" ? raw.kind : typeof raw.type === "string" ? raw.type : ""; | |
| const kind = kindRaw.trim().toLowerCase(); | |
| if (kind !== "bundled" && kind !== "npm" && kind !== "git") { | |
| return undefined; | |
| } | |
| const spec: HookInstallSpec = { | |
| kind: kind, | |
| }; | |
| if (typeof raw.id === "string") { | |
| spec.id = raw.id; | |
| } | |
| if (typeof raw.label === "string") { | |
| spec.label = raw.label; | |
| } | |
| const bins = normalizeStringList(raw.bins); | |
| if (bins.length > 0) { | |
| spec.bins = bins; | |
| } | |
| if (typeof raw.package === "string") { | |
| spec.package = raw.package; | |
| } | |
| if (typeof raw.repository === "string") { | |
| spec.repository = raw.repository; | |
| } | |
| return spec; | |
| } | |
| function getFrontmatterValue(frontmatter: ParsedHookFrontmatter, key: string): string | undefined { | |
| const raw = frontmatter[key]; | |
| return typeof raw === "string" ? raw : undefined; | |
| } | |
| function parseFrontmatterBool(value: string | undefined, fallback: boolean): boolean { | |
| const parsed = parseBooleanValue(value); | |
| return parsed === undefined ? fallback : parsed; | |
| } | |
| export function resolveOpenClawMetadata( | |
| frontmatter: ParsedHookFrontmatter, | |
| ): OpenClawHookMetadata | undefined { | |
| const raw = getFrontmatterValue(frontmatter, "metadata"); | |
| if (!raw) { | |
| return undefined; | |
| } | |
| try { | |
| const parsed = JSON5.parse(raw); | |
| if (!parsed || typeof parsed !== "object") { | |
| return undefined; | |
| } | |
| const metadataRawCandidates = [MANIFEST_KEY, ...LEGACY_MANIFEST_KEYS]; | |
| let metadataRaw: unknown; | |
| for (const key of metadataRawCandidates) { | |
| const candidate = parsed[key]; | |
| if (candidate && typeof candidate === "object") { | |
| metadataRaw = candidate; | |
| break; | |
| } | |
| } | |
| if (!metadataRaw || typeof metadataRaw !== "object") { | |
| return undefined; | |
| } | |
| const metadataObj = metadataRaw as Record<string, unknown>; | |
| const requiresRaw = | |
| typeof metadataObj.requires === "object" && metadataObj.requires !== null | |
| ? (metadataObj.requires as Record<string, unknown>) | |
| : undefined; | |
| const installRaw = Array.isArray(metadataObj.install) ? (metadataObj.install as unknown[]) : []; | |
| const install = installRaw | |
| .map((entry) => parseInstallSpec(entry)) | |
| .filter((entry): entry is HookInstallSpec => Boolean(entry)); | |
| const osRaw = normalizeStringList(metadataObj.os); | |
| const eventsRaw = normalizeStringList(metadataObj.events); | |
| return { | |
| always: typeof metadataObj.always === "boolean" ? metadataObj.always : undefined, | |
| emoji: typeof metadataObj.emoji === "string" ? metadataObj.emoji : undefined, | |
| homepage: typeof metadataObj.homepage === "string" ? metadataObj.homepage : undefined, | |
| hookKey: typeof metadataObj.hookKey === "string" ? metadataObj.hookKey : undefined, | |
| export: typeof metadataObj.export === "string" ? metadataObj.export : undefined, | |
| os: osRaw.length > 0 ? osRaw : undefined, | |
| events: eventsRaw.length > 0 ? eventsRaw : [], | |
| requires: requiresRaw | |
| ? { | |
| bins: normalizeStringList(requiresRaw.bins), | |
| anyBins: normalizeStringList(requiresRaw.anyBins), | |
| env: normalizeStringList(requiresRaw.env), | |
| config: normalizeStringList(requiresRaw.config), | |
| } | |
| : undefined, | |
| install: install.length > 0 ? install : undefined, | |
| }; | |
| } catch { | |
| return undefined; | |
| } | |
| } | |
| export function resolveHookInvocationPolicy( | |
| frontmatter: ParsedHookFrontmatter, | |
| ): HookInvocationPolicy { | |
| return { | |
| enabled: parseFrontmatterBool(getFrontmatterValue(frontmatter, "enabled"), true), | |
| }; | |
| } | |
| export function resolveHookKey(hookName: string, entry?: HookEntry): string { | |
| return entry?.metadata?.hookKey ?? hookName; | |
| } | |