From bb76e41908a99fafcd221b444360ec71bb0466a0 Mon Sep 17 00:00:00 2001 From: Valentin Haudiquet Date: Fri, 18 Sep 2026 20:38:05 +0200 Subject: [PATCH] new: drive the wizard and verification offers through the Prompter port The interactive half of pkh new no longer touches the terminal prompt module directly: run() takes a Prompter, picks the wizard or the plain resolve path through interactive(), and every select/text/confirm question (including the verification offers) goes through the port. Cancellations propagate as Err, preserving Ctrl+C-aborts; the summary and vendoring-notice prints become log lines. A builder-server embed can now drive the whole scaffold wizard over its own wire format by implementing Prompter. --- src/main.rs | 8 ++- src/new/questions.rs | 140 +++++++++++++++++++++++-------------------- 2 files changed, 81 insertions(+), 67 deletions(-) diff --git a/src/main.rs b/src/main.rs index c37f330..73b02d6 100644 --- a/src/main.rs +++ b/src/main.rs @@ -321,10 +321,14 @@ fn main() { // the structural self-checks inside `scaffold` always run), with // the scaffold outcome (e.g. a failed vendoring) shaping the // offer. + let prompter = pkh::ui::prompt::TerminalPrompter; if let Err(e) = rt.block_on(async { - let opts = pkh::new::questions::run(cli).await?; + let opts = pkh::new::questions::run(cli, &prompter).await?; let outcome = pkh::new::scaffold(opts.clone(), &multi)?; - pkh::new::questions::offer_verification(&opts, &outcome, &multi, no_verify).await; + pkh::new::questions::offer_verification( + &opts, &outcome, &multi, no_verify, &prompter, + ) + .await; Ok::<(), Box>(()) }) { error!("{}", e); diff --git a/src/new/questions.rs b/src/new/questions.rs index 832d338..00a91f9 100644 --- a/src/new/questions.rs +++ b/src/new/questions.rs @@ -1,11 +1,11 @@ //! The `pkh new` interactive wizard. //! -//! [`run`] is the single entry point: on an interactive terminal it asks the -//! questions of the spec's "Proposed UX" transcript, fills a +//! [`run`] is the single entry point: when the prompter can interact it asks +//! the questions of the spec's "Proposed UX" transcript, fills a //! [`NewCli`] with the answers (explicit flags are never re-asked), and //! reuses [`options::resolve`] as the single source of truth for defaults, //! detection and validation — so the non-interactive and interactive paths -//! cannot drift apart. Without a terminal (or with `--defaults`) it goes +//! cannot drift apart. Headless (or with `--defaults`) it goes //! straight through [`options::resolve`], whose error lists every missing //! answer. //! @@ -13,11 +13,10 @@ //! verification builds of the spec ([`offer_verification`]); a failed //! verification never undoes the scaffold. //! -//! The prompt calls live in `run_wizard` and `offer_verification` only; +//! The prompter calls live in `run_wizard` and `offer_verification` only; //! everything else in this module is pure and unit-tested. use std::error::Error; -use std::io::IsTerminal; use std::path::PathBuf; use indicatif::MultiProgress; @@ -28,7 +27,7 @@ use crate::new::licenses; use crate::new::options::{self, NewCli, NewOptions, SourceDir, SourceFormat, TemplateId}; use crate::new::origin::GitOrigin; use crate::new::templates::{self, ProbeResult, ScaffoldOutcome}; -use crate::ui::prompt; +use crate::report::{Prompter, Validator}; /// Answer of the "where is the source code?" question: fresh skeleton. const SOURCE_SKELETON: &str = "Create a new project skeleton here"; @@ -40,8 +39,8 @@ const SOURCE_PATH: &str = "Package the sources in another directory…"; /// The "everything else" entry of the license menu. const LICENSE_OTHER: &str = "Other (enter a SPDX identifier)"; -/// Labels of the interactive `select` questions. `prompt::select` renders -/// `>