Download src/hooks/hooks-install.test.ts from SaylorTwift/openclaw: direct link, hf CLI and curl.
- Browser
- Download file 5.38 kB
-
https://huggingface.co/SaylorTwift/openclaw/resolve/main/src/hooks/hooks-install.test.ts
- Command line
-
hf download hf://SaylorTwift/openclaw/src/hooks/hooks-install.test.ts
-
curl -L -o hooks-install.test.ts https://huggingface.co/SaylorTwift/openclaw/resolve/main/src/hooks/hooks-install.test.ts
5.38 kB
| // Hook installs must preserve discovery and execution across copied and linked layouts. | |
| import fs from "node:fs/promises"; | |
| import path from "node:path"; | |
| import { afterEach, beforeEach, describe, expect, it } from "vitest"; | |
| import { installPluginWithHookFallback } from "../cli/plugins-install-hook-fallback.js"; | |
| import { readConfigFileSnapshot } from "../config/config.js"; | |
| import { loadConfigForInstall } from "../plugins/install-config.js"; | |
| import { | |
| createOpenClawTestState, | |
| type OpenClawTestState, | |
| } from "../test-utils/openclaw-test-state.js"; | |
| import { pinConfigDir } from "../utils.js"; | |
| import { readHookInstalls } from "./installs.js"; | |
| import { | |
| clearInternalHooks, | |
| createInternalHookEvent, | |
| setInternalHooksEnabled, | |
| triggerInternalHook, | |
| } from "./internal-hooks.js"; | |
| import { prepareInternalHooks } from "./loader.js"; | |
| import { loadWorkspaceHookEntries } from "./workspace.js"; | |
| async function writeHook(hookDir: string, name: string): Promise<void> { | |
| await fs.mkdir(hookDir, { recursive: true }); | |
| await fs.writeFile( | |
| path.join(hookDir, "HOOK.md"), | |
| [ | |
| "---", | |
| `name: ${name}`, | |
| 'description: "Test hook"', | |
| 'metadata: {"openclaw":{"events":["command:new"]}}', | |
| "---", | |
| "", | |
| ].join("\n"), | |
| "utf-8", | |
| ); | |
| await fs.writeFile( | |
| path.join(hookDir, "handler.js"), | |
| `export default async function(event) { event.messages.push(${JSON.stringify(name)}); }\n`, | |
| "utf-8", | |
| ); | |
| } | |
| describe.each([ | |
| { mode: "copied", link: false }, | |
| { mode: "linked", link: true }, | |
| ])("hooks install ($mode)", ({ link }) => { | |
| let state: OpenClawTestState; | |
| beforeEach(async () => { | |
| state = await createOpenClawTestState({ label: "hooks-install", layout: "split" }); | |
| pinConfigDir(); | |
| clearInternalHooks(); | |
| await state.writeConfig({ | |
| agents: { defaults: { workspace: state.workspaceDir } }, | |
| plugins: { enabled: false }, | |
| }); | |
| }); | |
| afterEach(async () => { | |
| try { | |
| const disabled = await prepareInternalHooks( | |
| { hooks: { internal: { enabled: false } } }, | |
| state.workspaceDir, | |
| ); | |
| disabled.commit(); | |
| clearInternalHooks(); | |
| setInternalHooksEnabled(true); | |
| } finally { | |
| await state.cleanup(); | |
| pinConfigDir(); | |
| } | |
| }); | |
| it.each([ | |
| { layout: "single hook", hookPath: ".", hookPackId: "hello-hook" }, | |
| { layout: "conventional pack", hookPath: "./hooks/hello-hook", hookPackId: "hello-hooks" }, | |
| { layout: "direct-child pack", hookPath: "./hello-hook", hookPackId: "hello-hooks" }, | |
| ])( | |
| "discovers and triggers only installed hooks from a $layout", | |
| async ({ hookPath, hookPackId }) => { | |
| const sourceDir = state.path("sources", "hook-pack"); | |
| await writeHook(path.resolve(sourceDir, hookPath), "hello-hook"); | |
| await writeHook(state.path("sources", "adjacent-hook"), "adjacent-hook"); | |
| if (hookPath !== ".") { | |
| await fs.writeFile( | |
| path.join(sourceDir, "package.json"), | |
| JSON.stringify({ | |
| name: "@acme/hello-hooks", | |
| version: "0.0.0", | |
| openclaw: { hooks: [hookPath] }, | |
| }), | |
| "utf-8", | |
| ); | |
| // A package manifest selects its hooks; neither root nor nested siblings are implicit hooks. | |
| await writeHook(path.join(sourceDir, "unlisted-root"), "unlisted-root"); | |
| await writeHook(path.join(sourceDir, "hooks", "unlisted-nested"), "unlisted-nested"); | |
| } | |
| const snapshot = await loadConfigForInstall({ | |
| rawSpec: sourceDir, | |
| }); | |
| const installResult = await installPluginWithHookFallback({ | |
| request: { source: "local", path: sourceDir, mode: "install", link }, | |
| snapshot, | |
| safetyOverrides: { config: snapshot.config }, | |
| }); | |
| expect(installResult).toEqual({ ok: true }); | |
| const installed = await readConfigFileSnapshot(); | |
| expect(installed.valid).toBe(true); | |
| expect(installed.config.hooks?.internal).toMatchObject({ | |
| enabled: true, | |
| entries: { "hello-hook": { enabled: true } }, | |
| }); | |
| expect(installed.config.hooks?.internal?.load?.extraDirs ?? []).toEqual( | |
| link ? [sourceDir] : [], | |
| ); | |
| const installPath = link ? sourceDir : state.statePath("hooks", hookPackId); | |
| expect(readHookInstalls()).toEqual({ | |
| [hookPackId]: expect.objectContaining({ | |
| source: "path", | |
| sourcePath: sourceDir, | |
| installPath, | |
| hooks: ["hello-hook"], | |
| }), | |
| }); | |
| await expect( | |
| fs.readFile(path.resolve(installPath, hookPath, "handler.js"), "utf-8"), | |
| ).resolves.toContain("hello-hook"); | |
| const options = { bundledHooksDir: state.path("bundled-none") }; | |
| const discovered = loadWorkspaceHookEntries(state.workspaceDir, { | |
| ...options, | |
| config: installed.config, | |
| }); | |
| const prepared = await prepareInternalHooks(installed.config, state.workspaceDir, options); | |
| prepared.commit(); | |
| const event = createInternalHookEvent("command", "new", "test-session"); | |
| await triggerInternalHook(event); | |
| expect({ | |
| discovered: discovered.map((entry) => entry.hook.name).toSorted(), | |
| loaded: prepared.loadedCount, | |
| messages: event.messages.toSorted(), | |
| }).toEqual({ | |
| discovered: ["hello-hook"], | |
| loaded: 1, | |
| messages: ["hello-hook"], | |
| }); | |
| }, | |
| ); | |
| }); | |