feat(shared): add safeCreateHook utility for error-safe hook creation

This commit is contained in:
YeonGyu-Kim
2026-02-07 13:32:45 +09:00
parent 1c0b41aa65
commit f9742ddfca
3 changed files with 98 additions and 0 deletions

View File

@@ -41,3 +41,4 @@ export * from "./tmux"
export * from "./model-suggestion-retry"
export * from "./opencode-server-auth"
export * from "./port-utils"
export * from "./safe-create-hook"

View File

@@ -0,0 +1,73 @@
import { describe, test, expect, spyOn, afterEach } from "bun:test"
import * as shared from "./logger"
import { safeCreateHook } from "./safe-create-hook"
afterEach(() => {
;(shared.log as any)?.mockRestore?.()
})
describe("safeCreateHook", () => {
test("returns hook object when factory succeeds", () => {
//#given
const hook = { handler: () => {} }
const factory = () => hook
//#when
const result = safeCreateHook("test-hook", factory)
//#then
expect(result).toBe(hook)
})
test("returns null when factory throws", () => {
//#given
spyOn(shared, "log").mockImplementation(() => {})
const factory = () => {
throw new Error("boom")
}
//#when
const result = safeCreateHook("test-hook", factory)
//#then
expect(result).toBeNull()
})
test("logs error when factory throws", () => {
//#given
const logSpy = spyOn(shared, "log").mockImplementation(() => {})
const factory = () => {
throw new Error("boom")
}
//#when
safeCreateHook("my-hook", factory)
//#then
expect(logSpy).toHaveBeenCalled()
const callArgs = logSpy.mock.calls[0]
expect(callArgs[0]).toContain("my-hook")
expect(callArgs[0]).toContain("Hook creation failed")
})
test("propagates error when enabled is false", () => {
//#given
const factory = () => {
throw new Error("boom")
}
//#when + #then
expect(() => safeCreateHook("test-hook", factory, { enabled: false })).toThrow("boom")
})
test("returns null for factory returning undefined", () => {
//#given
const factory = () => undefined as any
//#when
const result = safeCreateHook("test-hook", factory)
//#then
expect(result).toBeNull()
})
})

View File

@@ -0,0 +1,24 @@
import { log } from "./logger"
interface SafeCreateHookOptions {
enabled?: boolean
}
export function safeCreateHook<T>(
name: string,
factory: () => T,
options?: SafeCreateHookOptions,
): T | null {
const enabled = options?.enabled ?? true
if (!enabled) {
return factory() ?? null
}
try {
return factory() ?? null
} catch (error) {
log(`[safe-create-hook] Hook creation failed: ${name}`, { error })
return null
}
}