feat(cli): redesign database scope picker for searchable schema-first setup (#203)

* feat: add searchable setup prompt pickers

* fix: make snowflake scope discovery single query

* fix: make bigquery table discovery schema scoped

* fix: honor mysql and clickhouse database scope

* feat: wire schema scope discovery for all relational setup drivers

* feat: add schema-first database scope picker

* test: update setup prompt stubs for type-check

* docs: document database scope picker fields

* Fix database setup edit preservation

---------

Co-authored-by: Andrey Avtomonov <7889985+andreybavt@users.noreply.github.com>
This commit is contained in:
Andrey Avtomonov 2026-05-22 14:22:11 +02:00 committed by GitHub
parent fd2ba62d92
commit c87d14a554
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
30 changed files with 1530 additions and 331 deletions

View file

@ -1,5 +1,7 @@
import type { Writable } from 'node:stream';
import {
autocomplete,
autocompleteMultiselect,
cancel,
confirm,
intro,
@ -38,6 +40,22 @@ interface KtxSetupMultiselectOptions<Value extends string = string> {
cursorAt?: Value;
}
interface KtxSetupAutocompleteOptions<Value extends string = string> {
message: string;
options: Array<KtxSetupPromptOption<Value>>;
placeholder?: string;
maxItems?: number;
}
interface KtxSetupAutocompleteMultiselectOptions<Value extends string = string> {
message: string;
options: Array<KtxSetupPromptOption<Value>>;
placeholder?: string;
required?: boolean;
maxItems?: number;
initialValues?: Value[];
}
interface KtxSetupTextOptions {
message: string;
placeholder?: string;
@ -53,6 +71,8 @@ interface KtxSetupPasswordOptions {
export interface KtxSetupPromptAdapter {
select(options: KtxSetupSelectOptions): Promise<string>;
multiselect(options: KtxSetupMultiselectOptions): Promise<string[]>;
autocomplete(options: KtxSetupAutocompleteOptions): Promise<string>;
autocompleteMultiselect(options: KtxSetupAutocompleteMultiselectOptions): Promise<string[]>;
text(options: KtxSetupTextOptions): Promise<string | undefined>;
password(options: KtxSetupPasswordOptions): Promise<string | undefined>;
cancel(message: string): void;
@ -117,6 +137,50 @@ export function createKtxSetupPromptAdapter(options: KtxSetupPromptAdapterOption
return selected;
}
},
async autocomplete(promptOptions) {
const value = await withSetupInterruptConfirmation(() =>
autocomplete(withMenuOptionsSpacing(promptOptions)),
);
if (isCancel(value)) {
if (cancelOnSelectCancel) {
cancel(cancelMessage);
}
return options.selectCancelValue;
}
return String(value);
},
async autocompleteMultiselect(promptOptions) {
while (true) {
const value = await withSetupInterruptConfirmation(() =>
autocompleteMultiselect(withMenuOptionsSpacing(promptOptions)),
);
if (isCancel(value)) {
if (cancelOnMultiselectCancel) {
cancel(cancelMessage);
}
return [multiselectCancelValue];
}
const selected = [...value].map(String);
if (
selected.length === 0 &&
!promptOptions.required &&
options.confirmEmptyOptionalMultiselect === true
) {
const skipConfirmed = await confirm({
message: 'Nothing selected. Skip this step?',
initialValue: false,
});
if (isCancel(skipConfirmed)) {
cancel(cancelMessage);
return [multiselectCancelValue];
}
if (!skipConfirmed) {
continue;
}
}
return selected;
}
},
async text(promptOptions) {
const value = await withSetupInterruptConfirmation(() =>
text({ ...promptOptions, message: withTextInputNavigation(promptOptions.message) }),