/** * Shared command context: resolves config from global flags + file + env, * builds a Client, and normalizes error handling. */ import { Command } from 'commander'; import { resolveConfig } from './config'; import { Client, ApiError } from './client'; import { describeError } from './request'; export interface Ctx { client: Client; json: boolean; baseId?: string; /** The running subcommand (last in the chain). Use .opts() for its options. */ command: Command; program: Command; } /** * Build a Ctx. `command` is the subcommand being executed (so .opts() returns * its local options); `program` is the top-level program (for global options). */ export function makeCtx(program: Command, command: Command): Ctx { const g = program.opts() as { endpoint?: string; token?: string; baseId?: string; json?: boolean; }; const cfg = resolveConfig({ endpoint: g.endpoint, token: g.token, baseId: g.baseId, }); return { client: new Client(cfg), json: !!g.json, baseId: cfg.baseId, command, program, }; } /** * Wrap an async command action with normalized error handling. * * The wrapped fn receives (ctx, positionalArgs[], options). commander passes * positional args before the trailing Command; we detect the trailing Command * and forward everything else as positional, plus parsed options. */ export function run( fn: (ctx: Ctx, args: TArgs, options: Record) => Promise ): (this: Command, ...cmdArgs: unknown[]) => Promise { return async function (this: Command, ...cmdArgs: unknown[]) { // commander convention: last arg is the Command instance (the subcommand). const command = cmdArgs[cmdArgs.length - 1] as Command; const program = command?.parent ? findRoot(command) : command; const positional = cmdArgs.slice(0, -1) as TArgs; const options = (command?.opts() || {}) as Record; const ctx = makeCtx(program, command); try { await fn(ctx, positional, options); } catch (err) { const { status, code, message } = describeError(err); if (ctx.json) { process.stdout.write(JSON.stringify({ error: { status, code, message } }) + '\n'); } else { process.stderr.write(`✗ ${code}${status ? ` (${status})` : ''}: ${message}\n`); } process.exitCode = 1; } }; } function findRoot(cmd: Command): Command { let c = cmd; while (c.parent) c = c.parent; return c; } /** Require a baseId: from flag, else ctx default, else throw with guidance. */ export function requireBaseId(ctx: Ctx, flagVal?: string): string { const baseId = flagVal || ctx.baseId; if (!baseId) { throw new ApiError( 0, 'MISSING_BASE_ID', 'No base id. Pass --base-id , set a default with `inkreach auth login --base-id `, or export INKREACH_BASE_ID.' ); } return baseId; }