mirror of
https://github.com/jackwener/OpenCLI.git
synced 2026-09-14 18:25:42 +08:00
15c6d0d508
- Extract shared parseYamlArgs() to yaml-schema.ts, eliminating duplicate YAML args parsing in discovery.ts and build-manifest.ts - Unify BROWSER_ONLY_STEPS: export from capabilityRouting.ts, reuse in pipeline executor (fixes missing intercept/tap in retry set) - Remove dead normalizeArgValue from commanderAdapter; bool coercion now handled solely by coerceAndValidateArgs in execution.ts - Add closeWindow?() to IPage interface, replacing unsafe casts in executor - BrowserBridge/CDPBridge implement IBrowserFactory, removing double cast in getBrowserFactory() - Simplify CliError subclasses with new.target.name (9 redundant this.name assignments removed) - Add hook dedup in addHook() to prevent duplicate registrations - Fix normalizeRows to safely handle primitive values - Unify CommandArgs type: execution.ts now imports from registry.ts - Cache strategyLabel() call in cli.ts list command
92 lines
3.1 KiB
TypeScript
92 lines
3.1 KiB
TypeScript
/**
|
|
* Plugin lifecycle hooks: allows plugins to tap into opencli's execution lifecycle.
|
|
*
|
|
* Hooks use globalThis (like the command registry) to guarantee a single shared
|
|
* instance across all module copies — critical when TS plugins are loaded via
|
|
* npm link / peerDependency symlinks.
|
|
*
|
|
* Available hooks:
|
|
* onStartup — fired once after all commands & plugins are discovered
|
|
* onBeforeExecute — fired before every command execution
|
|
* onAfterExecute — fired after every command execution (receives result)
|
|
*/
|
|
|
|
import { log } from './logger.js';
|
|
|
|
export type HookName = 'onStartup' | 'onBeforeExecute' | 'onAfterExecute';
|
|
|
|
export interface HookContext {
|
|
/** Command full name in "site/name" format, or "__startup__" for onStartup */
|
|
command: string;
|
|
/** Coerced and validated arguments */
|
|
args: Record<string, unknown>;
|
|
/** Epoch ms when execution started (set by executeCommand) */
|
|
startedAt?: number;
|
|
/** Epoch ms when execution finished (set by executeCommand) */
|
|
finishedAt?: number;
|
|
/** Error thrown by the command, if execution failed */
|
|
error?: unknown;
|
|
/** Plugins can attach arbitrary data here for cross-hook communication */
|
|
[key: string]: unknown;
|
|
}
|
|
|
|
export type HookFn = (ctx: HookContext, result?: unknown) => void | Promise<void>;
|
|
|
|
// ── Singleton hook store (shared across module instances via globalThis) ──
|
|
declare global {
|
|
// eslint-disable-next-line no-var
|
|
var __opencli_hooks__: Map<HookName, HookFn[]> | undefined;
|
|
}
|
|
const _hooks: Map<HookName, HookFn[]> =
|
|
globalThis.__opencli_hooks__ ??= new Map();
|
|
|
|
// ── Registration API (used by plugins) ─────────────────────────────────────
|
|
|
|
function addHook(name: HookName, fn: HookFn): void {
|
|
const list = _hooks.get(name) ?? [];
|
|
if (list.includes(fn)) return;
|
|
list.push(fn);
|
|
_hooks.set(name, list);
|
|
}
|
|
|
|
/** Register a hook that fires once after all plugins are discovered. */
|
|
export function onStartup(fn: HookFn): void {
|
|
addHook('onStartup', fn);
|
|
}
|
|
|
|
/** Register a hook that fires before every command execution. */
|
|
export function onBeforeExecute(fn: HookFn): void {
|
|
addHook('onBeforeExecute', fn);
|
|
}
|
|
|
|
/** Register a hook that fires after every command execution with the result. */
|
|
export function onAfterExecute(fn: HookFn): void {
|
|
addHook('onAfterExecute', fn);
|
|
}
|
|
|
|
// ── Emit API (used internally by opencli core) ─────────────────────────────
|
|
|
|
/**
|
|
* Trigger all registered handlers for a hook.
|
|
* Each handler is wrapped in try/catch — a failing hook never blocks command execution.
|
|
*/
|
|
export async function emitHook(name: HookName, ctx: HookContext, result?: unknown): Promise<void> {
|
|
const handlers = _hooks.get(name);
|
|
if (!handlers || handlers.length === 0) return;
|
|
|
|
for (const fn of handlers) {
|
|
try {
|
|
await fn(ctx, result);
|
|
} catch (err) {
|
|
log.warn(`Hook ${name} handler failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Remove all registered hooks. Intended for testing only.
|
|
*/
|
|
export function clearAllHooks(): void {
|
|
_hooks.clear();
|
|
}
|