Files
suaveplan/packages/devtools/tooling/installer/src/host/prompts.ts
2026-05-27 23:29:53 +01:00

78 lines
2.8 KiB
TypeScript

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<TAnswers> {
readonly plugin: InstallerPlugin<TAnswers, unknown>;
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<TAnswers>(
options: RunPromptsOptions<TAnswers>
): Promise<TAnswers> {
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();
}
}
);
}