Download src/hooks/frontmatter.test.ts from SaylorTwift/openclaw: direct link, hf CLI and curl.
- Browser
- Download file 9.5 kB
-
https://huggingface.co/SaylorTwift/openclaw/resolve/main/src/hooks/frontmatter.test.ts
- Command line
-
hf download hf://SaylorTwift/openclaw/src/hooks/frontmatter.test.ts
-
curl -L -o frontmatter.test.ts https://huggingface.co/SaylorTwift/openclaw/resolve/main/src/hooks/frontmatter.test.ts
9.5 kB
| // Hook frontmatter tests cover hook metadata parsing from hook files. | |
| import { expectDefined } from "@openclaw/normalization-core"; | |
| import { describe, expect, it } from "vitest"; | |
| import { | |
| parseHookFrontmatter, | |
| resolveHookManifestMetadata, | |
| resolveHookInvocationPolicy, | |
| } from "./frontmatter.js"; | |
| import type { OpenClawHookMetadata } from "./types.js"; | |
| function requireString(value: string | undefined, label: string): string { | |
| if (typeof value !== "string") { | |
| throw new Error(`expected ${label}`); | |
| } | |
| return value; | |
| } | |
| function requireOpenClawMetadata(metadata: OpenClawHookMetadata | undefined): OpenClawHookMetadata { | |
| if (!metadata) { | |
| throw new Error("expected openclaw metadata"); | |
| } | |
| return metadata; | |
| } | |
| describe("parseHookFrontmatter", () => { | |
| it("parses single-line key-value pairs", () => { | |
| const content = `--- | |
| name: test-hook | |
| description: "A test hook" | |
| homepage: https://example.com | |
| --- | |
| # Test Hook | |
| `; | |
| const result = parseHookFrontmatter(content); | |
| expect(result.name).toBe("test-hook"); | |
| expect(result.description).toBe("A test hook"); | |
| expect(result.homepage).toBe("https://example.com"); | |
| }); | |
| it("handles missing frontmatter", () => { | |
| const content = "# Just a markdown file"; | |
| const result = parseHookFrontmatter(content); | |
| expect(result).toStrictEqual({}); | |
| }); | |
| it("handles unclosed frontmatter", () => { | |
| const content = `--- | |
| name: broken | |
| `; | |
| const result = parseHookFrontmatter(content); | |
| expect(result).toStrictEqual({}); | |
| }); | |
| it("parses multi-line metadata block with indented JSON", () => { | |
| const content = `--- | |
| name: session-memory | |
| description: "Save session context" | |
| metadata: | |
| { | |
| "openclaw": { | |
| "emoji": "๐พ", | |
| "events": ["command:new"] | |
| } | |
| } | |
| --- | |
| # Session Memory Hook | |
| `; | |
| const result = parseHookFrontmatter(content); | |
| expect(result.name).toBe("session-memory"); | |
| expect(result.description).toBe("Save session context"); | |
| const metadata = requireString(result.metadata, "session-memory metadata"); | |
| // Verify the metadata is valid JSON | |
| const parsed = JSON.parse(metadata); | |
| expect(parsed.openclaw.emoji).toBe("๐พ"); | |
| expect(parsed.openclaw.events).toEqual(["command:new"]); | |
| }); | |
| it("parses multi-line metadata with complex nested structure", () => { | |
| const content = `--- | |
| name: command-logger | |
| description: "Log all command events" | |
| metadata: | |
| { | |
| "openclaw": | |
| { | |
| "emoji": "๐", | |
| "events": ["command"], | |
| "requires": { "config": ["workspace.dir"] }, | |
| "install": [{ "id": "bundled", "kind": "bundled", "label": "Bundled" }] | |
| } | |
| } | |
| --- | |
| `; | |
| const result = parseHookFrontmatter(content); | |
| expect(result.name).toBe("command-logger"); | |
| const parsed = JSON.parse(requireString(result.metadata, "command-logger metadata")); | |
| expect(parsed.openclaw.emoji).toBe("๐"); | |
| expect(parsed.openclaw.events).toEqual(["command"]); | |
| expect(parsed.openclaw.requires.config).toEqual(["workspace.dir"]); | |
| expect(parsed.openclaw.install[0].kind).toBe("bundled"); | |
| }); | |
| it("handles single-line metadata (inline JSON)", () => { | |
| const content = `--- | |
| name: simple-hook | |
| metadata: {"openclaw": {"events": ["test"]}} | |
| --- | |
| `; | |
| const result = parseHookFrontmatter(content); | |
| expect(result.name).toBe("simple-hook"); | |
| expect(result.metadata).toBe('{"openclaw": {"events": ["test"]}}'); | |
| }); | |
| it("handles mixed single-line and multi-line values", () => { | |
| const content = `--- | |
| name: mixed-hook | |
| description: "A hook with mixed values" | |
| homepage: https://example.com | |
| metadata: | |
| { | |
| "openclaw": { | |
| "events": ["command:new"] | |
| } | |
| } | |
| enabled: true | |
| --- | |
| `; | |
| const result = parseHookFrontmatter(content); | |
| expect(result.name).toBe("mixed-hook"); | |
| expect(result.description).toBe("A hook with mixed values"); | |
| expect(result.homepage).toBe("https://example.com"); | |
| expect(requireString(result.metadata, "mixed-hook metadata")).toContain('"command:new"'); | |
| expect(result.enabled).toBe("true"); | |
| }); | |
| it("strips surrounding quotes from values", () => { | |
| const content = `--- | |
| name: "quoted-name" | |
| description: 'single-quoted' | |
| --- | |
| `; | |
| const result = parseHookFrontmatter(content); | |
| expect(result.name).toBe("quoted-name"); | |
| expect(result.description).toBe("single-quoted"); | |
| }); | |
| it("handles CRLF line endings", () => { | |
| const content = "---\r\nname: test\r\ndescription: crlf\r\n---\r\n"; | |
| const result = parseHookFrontmatter(content); | |
| expect(result.name).toBe("test"); | |
| expect(result.description).toBe("crlf"); | |
| }); | |
| it("handles CR line endings", () => { | |
| const content = "---\rname: test\rdescription: cr\r---\r"; | |
| const result = parseHookFrontmatter(content); | |
| expect(result.name).toBe("test"); | |
| expect(result.description).toBe("cr"); | |
| }); | |
| }); | |
| describe("resolveHookManifestMetadata", () => { | |
| it("extracts openclaw metadata from parsed frontmatter", () => { | |
| const frontmatter = { | |
| name: "test-hook", | |
| metadata: JSON.stringify({ | |
| openclaw: { | |
| emoji: "๐ฅ", | |
| events: ["command:new", "command:reset"], | |
| requires: { | |
| config: ["workspace.dir"], | |
| bins: ["git"], | |
| }, | |
| }, | |
| }), | |
| }; | |
| const result = resolveHookManifestMetadata(frontmatter); | |
| const openclaw = requireOpenClawMetadata(result); | |
| expect(openclaw.emoji).toBe("๐ฅ"); | |
| expect(openclaw.events).toEqual(["command:new", "command:reset"]); | |
| expect(openclaw.requires?.config).toEqual(["workspace.dir"]); | |
| expect(openclaw.requires?.bins).toEqual(["git"]); | |
| }); | |
| it("returns undefined when metadata is missing", () => { | |
| const frontmatter = { name: "no-metadata" }; | |
| const result = resolveHookManifestMetadata(frontmatter); | |
| expect(result).toBeUndefined(); | |
| }); | |
| it("returns undefined when openclaw key is missing", () => { | |
| const frontmatter = { | |
| metadata: JSON.stringify({ other: "data" }), | |
| }; | |
| const result = resolveHookManifestMetadata(frontmatter); | |
| expect(result).toBeUndefined(); | |
| }); | |
| it("returns undefined for invalid JSON", () => { | |
| const frontmatter = { | |
| metadata: "not valid json {", | |
| }; | |
| const result = resolveHookManifestMetadata(frontmatter); | |
| expect(result).toBeUndefined(); | |
| }); | |
| it("handles install specs", () => { | |
| const frontmatter = { | |
| metadata: JSON.stringify({ | |
| openclaw: { | |
| events: ["command"], | |
| install: [ | |
| { id: "bundled", kind: "bundled", label: "Bundled with OpenClaw" }, | |
| { id: "npm", kind: "npm", package: "@openclaw/hook" }, | |
| ], | |
| }, | |
| }), | |
| }; | |
| const result = resolveHookManifestMetadata(frontmatter); | |
| expect(result?.install).toHaveLength(2); | |
| expect(expectDefined(result?.install?.[0], "result?.install?.[0] test invariant").kind).toBe( | |
| "bundled", | |
| ); | |
| expect(expectDefined(result?.install?.[1], "result?.install?.[1] test invariant").kind).toBe( | |
| "npm", | |
| ); | |
| expect(expectDefined(result?.install?.[1], "result?.install?.[1] test invariant").package).toBe( | |
| "@openclaw/hook", | |
| ); | |
| }); | |
| it("handles os restrictions", () => { | |
| const frontmatter = { | |
| metadata: JSON.stringify({ | |
| openclaw: { | |
| events: ["command"], | |
| os: ["darwin", "linux"], | |
| }, | |
| }), | |
| }; | |
| const result = resolveHookManifestMetadata(frontmatter); | |
| expect(result?.os).toEqual(["darwin", "linux"]); | |
| }); | |
| it("parses real session-memory HOOK.md format", () => { | |
| // This is the actual format used in the bundled hooks | |
| const content = `--- | |
| name: session-memory | |
| description: "Save session context to memory when a session is reset" | |
| homepage: https://docs.openclaw.ai/automation/hooks#session-memory | |
| metadata: | |
| { | |
| "openclaw": | |
| { | |
| "emoji": "๐พ", | |
| "events": ["command:new", "command:reset", "session:auto-reset"], | |
| "requires": { "config": ["workspace.dir"] }, | |
| "install": [{ "id": "bundled", "kind": "bundled", "label": "Bundled with OpenClaw" }], | |
| }, | |
| } | |
| --- | |
| # Session Memory Hook | |
| `; | |
| const frontmatter = parseHookFrontmatter(content); | |
| expect(frontmatter.name).toBe("session-memory"); | |
| expect(requireString(frontmatter.metadata, "session-memory metadata")).toContain( | |
| '"command:reset"', | |
| ); | |
| const openclaw = requireOpenClawMetadata(resolveHookManifestMetadata(frontmatter)); | |
| expect(openclaw.emoji).toBe("๐พ"); | |
| expect(openclaw.events).toEqual(["command:new", "command:reset", "session:auto-reset"]); | |
| expect(openclaw.requires?.config).toEqual(["workspace.dir"]); | |
| expect(expectDefined(openclaw.install?.[0], "openclaw.install?.[0] test invariant").kind).toBe( | |
| "bundled", | |
| ); | |
| }); | |
| it("parses YAML metadata map", () => { | |
| const content = `--- | |
| name: yaml-metadata | |
| metadata: | |
| openclaw: | |
| emoji: disk | |
| events: | |
| - command:new | |
| --- | |
| `; | |
| const frontmatter = parseHookFrontmatter(content); | |
| const openclaw = resolveHookManifestMetadata(frontmatter); | |
| expect(openclaw?.emoji).toBe("disk"); | |
| expect(openclaw?.events).toEqual(["command:new"]); | |
| }); | |
| }); | |
| describe("resolveHookInvocationPolicy", () => { | |
| it("defaults to enabled when missing", () => { | |
| expect(resolveHookInvocationPolicy({}).enabled).toBe(true); | |
| }); | |
| it("parses enabled flag", () => { | |
| expect(resolveHookInvocationPolicy({ enabled: "no" }).enabled).toBe(false); | |
| expect(resolveHookInvocationPolicy({ enabled: "on" }).enabled).toBe(true); | |
| }); | |
| }); | |