import { trace } from "@opentelemetry/api"; import { InstallerError } from "../error-codes.js"; import type { InstallerContext, InstallerPlugin, } from "../types/installer.types.js"; const tracer = trace.getTracer("@suaveplan/installer", "0.1.0"); export interface RunPromptsOptions { readonly plugin: InstallerPlugin; readonly ctx: InstallerContext; readonly explicitAnswers?: TAnswers; readonly answersFromFile?: unknown; } /** * Run the plugin's optional `prompts` stage. If the plugin declares a * schema and answers are available (via `explicitAnswers` or * `answersFromFile`), validate them. When the plugin demands additional * answers but `--no-interactive` is set and none were supplied, fail with * `INSTALLER.PROMPT_VALIDATION_FAILED`. */ export async function runPrompts( options: RunPromptsOptions ): Promise { const { plugin, ctx, explicitAnswers, answersFromFile } = options; return tracer.startActiveSpan( "installer.prompts", { attributes: { "plugin.id": plugin.id, "plugin.version": plugin.version, dry_run: ctx.dryRun, }, }, async (span) => { try { if (!plugin.prompts) { if (explicitAnswers !== undefined) return explicitAnswers; return {} as TAnswers; } const schema = plugin.prompts(ctx); const candidate = explicitAnswers !== undefined ? explicitAnswers : answersFromFile; if (candidate === undefined) { if (ctx.noInteractive) { throw new InstallerError( "INSTALLER.PROMPT_VALIDATION_FAILED", "installer.prompts.no_answers_in_no_interactive" ); } throw new InstallerError( "INSTALLER.PROMPT_VALIDATION_FAILED", "installer.prompts.missing_answers" ); } const result = schema.safeParse(candidate); if (!result.success) { throw new InstallerError( "INSTALLER.PROMPT_VALIDATION_FAILED", "installer.prompts.invalid", { cause: result.error as unknown as Error, context: { issues: result.error.issues }, } ); } return result.data; } finally { span.end(); } } ); }