From 9a29d7833ba61185d2dc39126c6c72c8e9374124 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Mon, 16 Feb 2026 17:57:20 +0000 Subject: [PATCH] refactor(cli): dedupe browser and hooks command handlers --- src/cli/browser-cli-state.ts | 236 +++++++++++++---------------------- src/cli/hooks-cli.ts | 132 +++++++++----------- 2 files changed, 147 insertions(+), 221 deletions(-) diff --git a/src/cli/browser-cli-state.ts b/src/cli/browser-cli-state.ts index ace701bd236..db4a8a452da 100644 --- a/src/cli/browser-cli-state.ts +++ b/src/cli/browser-cli-state.ts @@ -19,6 +19,32 @@ function runBrowserCommand(action: () => Promise) { }); } +async function runBrowserSetRequest(params: { + parent: BrowserParentOpts; + path: string; + body: Record; + successMessage: string; +}) { + await runBrowserCommand(async () => { + const profile = params.parent?.browserProfile; + const result = await callBrowserRequest( + params.parent, + { + method: "POST", + path: params.path, + query: profile ? { profile } : undefined, + body: params.body, + }, + { timeoutMs: 20000 }, + ); + if (params.parent?.json) { + defaultRuntime.log(JSON.stringify(result, null, 2)); + return; + } + defaultRuntime.log(params.successMessage); + }); +} + export function registerBrowserStateCommands( browser: Command, parentOpts: (cmd: Command) => BrowserParentOpts, @@ -56,32 +82,20 @@ export function registerBrowserStateCommands( .option("--target-id ", "CDP target id (or unique prefix)") .action(async (value: string, opts, cmd) => { const parent = parentOpts(cmd); - const profile = parent?.browserProfile; const offline = parseOnOff(value); if (offline === null) { defaultRuntime.error(danger("Expected on|off")); defaultRuntime.exit(1); return; } - await runBrowserCommand(async () => { - const result = await callBrowserRequest( - parent, - { - method: "POST", - path: "/set/offline", - query: profile ? { profile } : undefined, - body: { - offline, - targetId: opts.targetId?.trim() || undefined, - }, - }, - { timeoutMs: 20000 }, - ); - if (parent?.json) { - defaultRuntime.log(JSON.stringify(result, null, 2)); - return; - } - defaultRuntime.log(`offline: ${offline}`); + await runBrowserSetRequest({ + parent, + path: "/set/offline", + body: { + offline, + targetId: opts.targetId?.trim() || undefined, + }, + successMessage: `offline: ${offline}`, }); }); @@ -92,7 +106,6 @@ export function registerBrowserStateCommands( .option("--target-id ", "CDP target id (or unique prefix)") .action(async (opts, cmd) => { const parent = parentOpts(cmd); - const profile = parent?.browserProfile; await runBrowserCommand(async () => { const parsed = JSON.parse(String(opts.json)) as unknown; if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) { @@ -104,6 +117,7 @@ export function registerBrowserStateCommands( headers[k] = v; } } + const profile = parent?.browserProfile; const result = await callBrowserRequest( parent, { @@ -134,28 +148,16 @@ export function registerBrowserStateCommands( .option("--target-id ", "CDP target id (or unique prefix)") .action(async (username: string | undefined, password: string | undefined, opts, cmd) => { const parent = parentOpts(cmd); - const profile = parent?.browserProfile; - await runBrowserCommand(async () => { - const result = await callBrowserRequest( - parent, - { - method: "POST", - path: "/set/credentials", - query: profile ? { profile } : undefined, - body: { - username: username?.trim() || undefined, - password, - clear: Boolean(opts.clear), - targetId: opts.targetId?.trim() || undefined, - }, - }, - { timeoutMs: 20000 }, - ); - if (parent?.json) { - defaultRuntime.log(JSON.stringify(result, null, 2)); - return; - } - defaultRuntime.log(opts.clear ? "credentials cleared" : "credentials set"); + await runBrowserSetRequest({ + parent, + path: "/set/credentials", + body: { + username: username?.trim() || undefined, + password, + clear: Boolean(opts.clear), + targetId: opts.targetId?.trim() || undefined, + }, + successMessage: opts.clear ? "credentials cleared" : "credentials set", }); }); @@ -170,30 +172,18 @@ export function registerBrowserStateCommands( .option("--target-id ", "CDP target id (or unique prefix)") .action(async (latitude: number | undefined, longitude: number | undefined, opts, cmd) => { const parent = parentOpts(cmd); - const profile = parent?.browserProfile; - await runBrowserCommand(async () => { - const result = await callBrowserRequest( - parent, - { - method: "POST", - path: "/set/geolocation", - query: profile ? { profile } : undefined, - body: { - latitude: Number.isFinite(latitude) ? latitude : undefined, - longitude: Number.isFinite(longitude) ? longitude : undefined, - accuracy: Number.isFinite(opts.accuracy) ? opts.accuracy : undefined, - origin: opts.origin?.trim() || undefined, - clear: Boolean(opts.clear), - targetId: opts.targetId?.trim() || undefined, - }, - }, - { timeoutMs: 20000 }, - ); - if (parent?.json) { - defaultRuntime.log(JSON.stringify(result, null, 2)); - return; - } - defaultRuntime.log(opts.clear ? "geolocation cleared" : "geolocation set"); + await runBrowserSetRequest({ + parent, + path: "/set/geolocation", + body: { + latitude: Number.isFinite(latitude) ? latitude : undefined, + longitude: Number.isFinite(longitude) ? longitude : undefined, + accuracy: Number.isFinite(opts.accuracy) ? opts.accuracy : undefined, + origin: opts.origin?.trim() || undefined, + clear: Boolean(opts.clear), + targetId: opts.targetId?.trim() || undefined, + }, + successMessage: opts.clear ? "geolocation cleared" : "geolocation set", }); }); @@ -204,7 +194,6 @@ export function registerBrowserStateCommands( .option("--target-id ", "CDP target id (or unique prefix)") .action(async (value: string, opts, cmd) => { const parent = parentOpts(cmd); - const profile = parent?.browserProfile; const v = value.trim().toLowerCase(); const colorScheme = v === "dark" ? "dark" : v === "light" ? "light" : v === "none" ? "none" : null; @@ -213,25 +202,14 @@ export function registerBrowserStateCommands( defaultRuntime.exit(1); return; } - await runBrowserCommand(async () => { - const result = await callBrowserRequest( - parent, - { - method: "POST", - path: "/set/media", - query: profile ? { profile } : undefined, - body: { - colorScheme, - targetId: opts.targetId?.trim() || undefined, - }, - }, - { timeoutMs: 20000 }, - ); - if (parent?.json) { - defaultRuntime.log(JSON.stringify(result, null, 2)); - return; - } - defaultRuntime.log(`media colorScheme: ${colorScheme}`); + await runBrowserSetRequest({ + parent, + path: "/set/media", + body: { + colorScheme, + targetId: opts.targetId?.trim() || undefined, + }, + successMessage: `media colorScheme: ${colorScheme}`, }); }); @@ -242,26 +220,14 @@ export function registerBrowserStateCommands( .option("--target-id ", "CDP target id (or unique prefix)") .action(async (timezoneId: string, opts, cmd) => { const parent = parentOpts(cmd); - const profile = parent?.browserProfile; - await runBrowserCommand(async () => { - const result = await callBrowserRequest( - parent, - { - method: "POST", - path: "/set/timezone", - query: profile ? { profile } : undefined, - body: { - timezoneId, - targetId: opts.targetId?.trim() || undefined, - }, - }, - { timeoutMs: 20000 }, - ); - if (parent?.json) { - defaultRuntime.log(JSON.stringify(result, null, 2)); - return; - } - defaultRuntime.log(`timezone: ${timezoneId}`); + await runBrowserSetRequest({ + parent, + path: "/set/timezone", + body: { + timezoneId, + targetId: opts.targetId?.trim() || undefined, + }, + successMessage: `timezone: ${timezoneId}`, }); }); @@ -272,26 +238,14 @@ export function registerBrowserStateCommands( .option("--target-id ", "CDP target id (or unique prefix)") .action(async (locale: string, opts, cmd) => { const parent = parentOpts(cmd); - const profile = parent?.browserProfile; - await runBrowserCommand(async () => { - const result = await callBrowserRequest( - parent, - { - method: "POST", - path: "/set/locale", - query: profile ? { profile } : undefined, - body: { - locale, - targetId: opts.targetId?.trim() || undefined, - }, - }, - { timeoutMs: 20000 }, - ); - if (parent?.json) { - defaultRuntime.log(JSON.stringify(result, null, 2)); - return; - } - defaultRuntime.log(`locale: ${locale}`); + await runBrowserSetRequest({ + parent, + path: "/set/locale", + body: { + locale, + targetId: opts.targetId?.trim() || undefined, + }, + successMessage: `locale: ${locale}`, }); }); @@ -302,26 +256,14 @@ export function registerBrowserStateCommands( .option("--target-id ", "CDP target id (or unique prefix)") .action(async (name: string, opts, cmd) => { const parent = parentOpts(cmd); - const profile = parent?.browserProfile; - await runBrowserCommand(async () => { - const result = await callBrowserRequest( - parent, - { - method: "POST", - path: "/set/device", - query: profile ? { profile } : undefined, - body: { - name, - targetId: opts.targetId?.trim() || undefined, - }, - }, - { timeoutMs: 20000 }, - ); - if (parent?.json) { - defaultRuntime.log(JSON.stringify(result, null, 2)); - return; - } - defaultRuntime.log(`device: ${name}`); + await runBrowserSetRequest({ + parent, + path: "/set/device", + body: { + name, + targetId: opts.targetId?.trim() || undefined, + }, + successMessage: `device: ${name}`, }); }); } diff --git a/src/cli/hooks-cli.ts b/src/cli/hooks-cli.ts index 42bb391e223..6fb7027036b 100644 --- a/src/cli/hooks-cli.ts +++ b/src/cli/hooks-cli.ts @@ -152,6 +152,32 @@ function formatHookMissingSummary(hook: HookStatusEntry): string { return missing.join("; "); } +function exitHooksCliWithError(err: unknown): never { + defaultRuntime.error( + `${theme.error("Error:")} ${err instanceof Error ? err.message : String(err)}`, + ); + process.exit(1); +} + +async function runHooksCliAction(action: () => Promise | void): Promise { + try { + await action(); + } catch (err) { + exitHooksCliWithError(err); + } +} + +function createInstallLogger() { + return { + info: (msg: string) => defaultRuntime.log(msg), + warn: (msg: string) => defaultRuntime.log(theme.warn(msg)), + }; +} + +function logGatewayRestartHint() { + defaultRuntime.log("Restart the gateway to load hooks."); +} + async function readInstalledPackageVersion(dir: string): Promise { try { const raw = await fsp.readFile(path.join(dir, "package.json"), "utf-8"); @@ -469,80 +495,55 @@ export function registerHooksCli(program: Command): void { .option("--eligible", "Show only eligible hooks", false) .option("--json", "Output as JSON", false) .option("-v, --verbose", "Show more details including missing requirements", false) - .action(async (opts) => { - try { + .action(async (opts) => + runHooksCliAction(async () => { const config = loadConfig(); const report = buildHooksReport(config); defaultRuntime.log(formatHooksList(report, opts)); - } catch (err) { - defaultRuntime.error( - `${theme.error("Error:")} ${err instanceof Error ? err.message : String(err)}`, - ); - process.exit(1); - } - }); + }), + ); hooks .command("info ") .description("Show detailed information about a hook") .option("--json", "Output as JSON", false) - .action(async (name, opts) => { - try { + .action(async (name, opts) => + runHooksCliAction(async () => { const config = loadConfig(); const report = buildHooksReport(config); defaultRuntime.log(formatHookInfo(report, name, opts)); - } catch (err) { - defaultRuntime.error( - `${theme.error("Error:")} ${err instanceof Error ? err.message : String(err)}`, - ); - process.exit(1); - } - }); + }), + ); hooks .command("check") .description("Check hooks eligibility status") .option("--json", "Output as JSON", false) - .action(async (opts) => { - try { + .action(async (opts) => + runHooksCliAction(async () => { const config = loadConfig(); const report = buildHooksReport(config); defaultRuntime.log(formatHooksCheck(report, opts)); - } catch (err) { - defaultRuntime.error( - `${theme.error("Error:")} ${err instanceof Error ? err.message : String(err)}`, - ); - process.exit(1); - } - }); + }), + ); hooks .command("enable ") .description("Enable a hook") - .action(async (name) => { - try { + .action(async (name) => + runHooksCliAction(async () => { await enableHook(name); - } catch (err) { - defaultRuntime.error( - `${theme.error("Error:")} ${err instanceof Error ? err.message : String(err)}`, - ); - process.exit(1); - } - }); + }), + ); hooks .command("disable ") .description("Disable a hook") - .action(async (name) => { - try { + .action(async (name) => + runHooksCliAction(async () => { await disableHook(name); - } catch (err) { - defaultRuntime.error( - `${theme.error("Error:")} ${err instanceof Error ? err.message : String(err)}`, - ); - process.exit(1); - } - }); + }), + ); hooks .command("install") @@ -597,16 +598,13 @@ export function registerHooksCli(program: Command): void { await writeConfigFile(next); defaultRuntime.log(`Linked hook path: ${shortenHomePath(resolved)}`); - defaultRuntime.log(`Restart the gateway to load hooks.`); + logGatewayRestartHint(); return; } const result = await installHooksFromPath({ path: resolved, - logger: { - info: (msg) => defaultRuntime.log(msg), - warn: (msg) => defaultRuntime.log(theme.warn(msg)), - }, + logger: createInstallLogger(), }); if (!result.ok) { defaultRuntime.error(result.error); @@ -628,7 +626,7 @@ export function registerHooksCli(program: Command): void { await writeConfigFile(next); defaultRuntime.log(`Installed hooks: ${result.hooks.join(", ")}`); - defaultRuntime.log(`Restart the gateway to load hooks.`); + logGatewayRestartHint(); return; } @@ -652,10 +650,7 @@ export function registerHooksCli(program: Command): void { const result = await installHooksFromNpmSpec({ spec: raw, - logger: { - info: (msg) => defaultRuntime.log(msg), - warn: (msg) => defaultRuntime.log(theme.warn(msg)), - }, + logger: createInstallLogger(), }); if (!result.ok) { defaultRuntime.error(result.error); @@ -674,7 +669,7 @@ export function registerHooksCli(program: Command): void { }); await writeConfigFile(next); defaultRuntime.log(`Installed hooks: ${result.hooks.join(", ")}`); - defaultRuntime.log(`Restart the gateway to load hooks.`); + logGatewayRestartHint(); }); hooks @@ -726,10 +721,7 @@ export function registerHooksCli(program: Command): void { mode: "update", dryRun: true, expectedHookPackId: hookId, - logger: { - info: (msg) => defaultRuntime.log(msg), - warn: (msg) => defaultRuntime.log(theme.warn(msg)), - }, + logger: createInstallLogger(), }); if (!probe.ok) { defaultRuntime.log(theme.error(`Failed to check ${hookId}: ${probe.error}`)); @@ -750,10 +742,7 @@ export function registerHooksCli(program: Command): void { spec: record.spec, mode: "update", expectedHookPackId: hookId, - logger: { - info: (msg) => defaultRuntime.log(msg), - warn: (msg) => defaultRuntime.log(theme.warn(msg)), - }, + logger: createInstallLogger(), }); if (!result.ok) { defaultRuntime.log(theme.error(`Failed to update ${hookId}: ${result.error}`)); @@ -782,20 +771,15 @@ export function registerHooksCli(program: Command): void { if (updatedCount > 0) { await writeConfigFile(nextCfg); - defaultRuntime.log("Restart the gateway to load hooks."); + logGatewayRestartHint(); } }); - hooks.action(async () => { - try { + hooks.action(async () => + runHooksCliAction(async () => { const config = loadConfig(); const report = buildHooksReport(config); defaultRuntime.log(formatHooksList(report, {})); - } catch (err) { - defaultRuntime.error( - `${theme.error("Error:")} ${err instanceof Error ? err.message : String(err)}`, - ); - process.exit(1); - } - }); + }), + ); }