diff --git a/packages/cli/src/database-tree-picker.test.ts b/packages/cli/src/database-tree-picker.test.ts index 5559ee42..4dd1dca3 100644 --- a/packages/cli/src/database-tree-picker.test.ts +++ b/packages/cli/src/database-tree-picker.test.ts @@ -1,6 +1,7 @@ import { describe, expect, it, vi } from 'vitest'; import { pickDatabaseScope, + type DatabaseScopePromptAdapter, type DatabaseTreePickerRenderer, type PickDatabaseScopeArgs, } from './database-tree-picker.js'; @@ -12,8 +13,17 @@ function makeIo() { let stderr = ''; return { io: { - stdout: { isTTY: true, write: (chunk: string) => { stdout += chunk; } }, - stderr: { write: (chunk: string) => { stderr += chunk; } }, + stdout: { + isTTY: true, + write: (chunk: string) => { + stdout += chunk; + }, + }, + stderr: { + write: (chunk: string) => { + stderr += chunk; + }, + }, }, stdout: () => stdout, stderr: () => stderr, @@ -48,23 +58,96 @@ const discovered = [ { schema: 'public', name: 'sessions', kind: 'table' as const }, ]; +function promptAdapter(overrides: Partial = {}): DatabaseScopePromptAdapter { + return { + autocompleteMultiselect: vi.fn(async () => ['analytics']), + select: vi.fn(async () => 'refine'), + ...overrides, + }; +} + function baseArgs(overrides: Partial = {}): PickDatabaseScopeArgs { return { connectionId: 'warehouse', schemaNoun: 'schema', schemaNounPlural: 'schemas', - discovered, + schemas: ['analytics', 'public'], + schemaSuggestion: { excluded: new Set(), suggested: new Set(['analytics']) }, existing: { enabledTables: [] }, - defaultSchemas: ['analytics'], supportsSchemaScope: true, + listTablesForSchemas: vi.fn(async () => discovered), + prompts: promptAdapter(), ...overrides, }; } describe('pickDatabaseScope', () => { + it('starts Stage 1 with no checked schemas and does not enumerate tables before schema selection', async () => { + const prompts = promptAdapter({ + autocompleteMultiselect: vi.fn(async () => ['analytics']), + select: vi.fn(async () => 'save'), + }); + const listTablesForSchemas = vi.fn(async () => [ + { schema: 'analytics', name: 'orders', kind: 'table' as const }, + ]); + + const result = await pickDatabaseScope( + baseArgs({ + connectionId: 'warehouse', + schemaNoun: 'dataset', + schemaNounPlural: 'datasets', + schemas: ['analytics', 'raw'], + schemaSuggestion: { excluded: new Set(['raw']), suggested: new Set(['analytics']) }, + listTablesForSchemas, + prompts, + }), + makeIo().io, + captureRenderer().renderer, + ); + + expect(listTablesForSchemas).toHaveBeenCalledTimes(1); + expect(listTablesForSchemas).toHaveBeenCalledWith(['analytics']); + expect(result).toEqual({ + kind: 'selected', + activeSchemas: ['analytics'], + enabledTables: ['analytics.orders'], + }); + }); + + it('routes partial existing allowlists through Stage 2 so save preserves table selections', async () => { + const { renderer, setResult } = captureRenderer(); + setResult({ kind: 'save', selectedIds: ['analytics.customers'] }); + const prompts = promptAdapter({ + autocompleteMultiselect: vi.fn(async () => ['analytics']), + select: vi.fn(async () => 'save'), + }); + const listTablesForSchemas = vi.fn(async () => [ + { schema: 'analytics', name: 'customers', kind: 'table' as const }, + { schema: 'analytics', name: 'orders', kind: 'table' as const }, + ]); + + const result = await pickDatabaseScope( + baseArgs({ + schemas: ['analytics'], + schemaSuggestion: { excluded: new Set(), suggested: new Set(['analytics']) }, + existing: { enabledTables: ['analytics.customers'] }, + listTablesForSchemas, + prompts, + }), + makeIo().io, + renderer, + ); + + expect(result).toEqual({ + kind: 'selected', + activeSchemas: ['analytics'], + enabledTables: ['analytics.customers'], + }); + }); + it('builds a 2-level tree (schemas as parents, tables as children) and uses save-empty action', async () => { const { renderer, capture, setResult } = captureRenderer(); - setResult({ kind: 'quit' }); + setResult({ kind: 'save', selectedIds: ['analytics'] }); await pickDatabaseScope(baseArgs(), makeIo().io, renderer); @@ -81,18 +164,18 @@ describe('pickDatabaseScope', () => { expect(capture.state?.byId.get('public.events')?.title).toBe('events (view)'); }); - it('pre-checks default schemas at the parent level when no existing selection', async () => { + it('pre-checks selected schemas at the parent level when no existing selection reaches Stage 2', async () => { const { renderer, capture, setResult } = captureRenderer(); - setResult({ kind: 'quit' }); + setResult({ kind: 'save', selectedIds: ['analytics'] }); - await pickDatabaseScope(baseArgs({ defaultSchemas: ['analytics'] }), makeIo().io, renderer); + await pickDatabaseScope(baseArgs(), makeIo().io, renderer); expect([...(capture.state?.checked ?? [])]).toEqual(['analytics']); }); it('collapses an existing full-schema selection back into the parent check', async () => { const { renderer, capture, setResult } = captureRenderer(); - setResult({ kind: 'quit' }); + setResult({ kind: 'save', selectedIds: ['analytics'] }); await pickDatabaseScope( baseArgs({ existing: { enabledTables: ['analytics.customers', 'analytics.orders'] } }), @@ -105,7 +188,7 @@ describe('pickDatabaseScope', () => { it('keeps a partial existing selection at the leaf level', async () => { const { renderer, capture, setResult } = captureRenderer(); - setResult({ kind: 'quit' }); + setResult({ kind: 'save', selectedIds: ['analytics.customers'] }); await pickDatabaseScope( baseArgs({ existing: { enabledTables: ['analytics.customers'] } }), @@ -142,24 +225,6 @@ describe('pickDatabaseScope', () => { }); }); - it('treats empty save as enable-all', async () => { - const { renderer, setResult } = captureRenderer(); - setResult({ kind: 'save', selectedIds: [] }); - - const result = await pickDatabaseScope(baseArgs(), makeIo().io, renderer); - - expect(result).toEqual({ - kind: 'selected', - activeSchemas: ['analytics', 'public'], - enabledTables: [ - 'analytics.customers', - 'analytics.orders', - 'public.events', - 'public.sessions', - ], - }); - }); - it('omits activeSchemas when the driver does not support a schema scope', async () => { const { renderer, setResult } = captureRenderer(); setResult({ kind: 'save', selectedIds: ['analytics'] }); @@ -177,11 +242,12 @@ describe('pickDatabaseScope', () => { }); }); - it('returns back when the picker quits', async () => { - const { renderer, setResult } = captureRenderer(); - setResult({ kind: 'quit' }); + it('returns back when Stage 1 is cancelled', async () => { + const prompts = promptAdapter({ + autocompleteMultiselect: vi.fn(async () => ['back']), + }); - const result = await pickDatabaseScope(baseArgs(), makeIo().io, renderer); + const result = await pickDatabaseScope(baseArgs({ prompts }), makeIo().io, captureRenderer().renderer); expect(result).toEqual({ kind: 'back' }); }); diff --git a/packages/cli/src/database-tree-picker.ts b/packages/cli/src/database-tree-picker.ts index 34c44a03..8630bc29 100644 --- a/packages/cli/src/database-tree-picker.ts +++ b/packages/cli/src/database-tree-picker.ts @@ -43,15 +43,33 @@ interface ScopeSuggestion { suggested: Set; } +/** @internal */ +export interface DatabaseScopePromptAdapter { + autocompleteMultiselect(options: { + message: string; + options: Array<{ value: string; label: string; hint?: string; disabled?: boolean }>; + placeholder?: string; + required?: boolean; + maxItems?: number; + initialValues?: string[]; + }): Promise; + select(options: { + message: string; + options: Array<{ value: string; label: string; hint?: string; disabled?: boolean }>; + }): Promise; +} + export interface PickDatabaseScopeArgs { connectionId: string; schemaNoun: string; schemaNounPlural: string; - discovered: readonly KtxTableListEntry[]; + schemas: readonly string[]; + schemaSuggestion: ScopeSuggestion; existing: { enabledTables: readonly string[] }; - defaultSchemas: readonly string[]; - schemaSuggestion?: ScopeSuggestion; supportsSchemaScope: boolean; + listTablesForSchemas: (schemas: string[]) => Promise; + initialSchemas?: readonly string[]; + prompts: DatabaseScopePromptAdapter; } function qualifiedTableId(entry: KtxTableListEntry): string { @@ -167,12 +185,39 @@ function schemasFromEnabledTables(enabledTables: readonly string[]): string[] { return result; } -export async function pickDatabaseScope( - args: PickDatabaseScopeArgs, - io: KtxCliIo, - render: DatabaseTreePickerRenderer = defaultRenderer, -): Promise { - const { inputs, schemaIds, allTables } = buildTreeInputs(args.discovered); +function schemaOptions(args: PickDatabaseScopeArgs): Array<{ value: string; label: string; hint?: string }> { + return args.schemas + .filter((schema) => !args.schemaSuggestion.excluded.has(schema)) + .slice() + .sort((left, right) => { + const leftSuggested = args.schemaSuggestion.suggested.has(left); + const rightSuggested = args.schemaSuggestion.suggested.has(right); + if (leftSuggested !== rightSuggested) return leftSuggested ? -1 : 1; + return left.localeCompare(right); + }) + .map((schema) => ({ + value: schema, + label: schema, + ...(args.schemaSuggestion.suggested.has(schema) ? { hint: 'suggested' } : {}), + })); +} + +function initialStageOneSchemas(args: PickDatabaseScopeArgs): string[] { + if (args.existing.enabledTables.length > 0) { + return schemasFromEnabledTables(args.existing.enabledTables); + } + return [...(args.initialSchemas ?? [])]; +} + +async function runStageTwoTreePicker(input: { + args: PickDatabaseScopeArgs; + discovered: readonly KtxTableListEntry[]; + selectedSchemas: readonly string[]; + io: KtxCliIo; + render: DatabaseTreePickerRenderer; +}): Promise { + const { args, discovered, selectedSchemas, io, render } = input; + const { inputs, schemaIds, allTables } = buildTreeInputs(discovered); const tree = buildPickerTree(inputs); const byId = new Map(tree.map((node) => [node.id, node])); const tableCount = allTables.length; @@ -181,7 +226,7 @@ export async function pickDatabaseScope( const initialSelection = args.existing.enabledTables.length > 0 ? initialSelectionForExisting(args.existing.enabledTables, byId) - : initialSelectionFromDefaults(args.defaultSchemas, schemaIds); + : initialSelectionFromDefaults(selectedSchemas, schemaIds); const initialState = buildInitialState({ tree, @@ -214,3 +259,61 @@ export async function pickDatabaseScope( return { kind: 'selected', activeSchemas, enabledTables }; } + +export async function pickDatabaseScope( + args: PickDatabaseScopeArgs, + io: KtxCliIo, + render: DatabaseTreePickerRenderer = defaultRenderer, +): Promise { + let selectedSchemas = initialStageOneSchemas(args); + while (true) { + const pickedSchemas = await args.prompts.autocompleteMultiselect({ + message: `Choose ${args.schemaNounPlural} to enable for ${args.connectionId}\nType to filter. Space to select. Enter when done.`, + placeholder: `Search ${args.schemaNounPlural}`, + options: schemaOptions(args), + initialValues: selectedSchemas, + required: false, + }); + if (pickedSchemas.includes('back')) { + return { kind: 'back' }; + } + selectedSchemas = pickedSchemas; + if (selectedSchemas.length === 0) { + io.stderr.write(`Nothing selected - type to filter, or Escape to skip ${args.schemaNoun} scope.\n`); + continue; + } + + const action = await args.prompts.select({ + message: `Save ${selectedSchemas.length} ${selectedSchemas.length === 1 ? args.schemaNoun : args.schemaNounPlural} or refine tables?`, + options: [ + { value: 'save', label: 'Save selection' }, + { value: 'refine', label: 'Refine: choose individual tables' }, + { value: 'back', label: 'Back' }, + ], + }); + if (action === 'back') { + continue; + } + + const discovered = await args.listTablesForSchemas(selectedSchemas); + if (action === 'save' && args.existing.enabledTables.length === 0) { + return { + kind: 'selected', + activeSchemas: args.supportsSchemaScope ? selectedSchemas : [], + enabledTables: discovered.map(qualifiedTableId), + }; + } + + const refined = await runStageTwoTreePicker({ + args, + discovered, + selectedSchemas, + io, + render, + }); + if (refined.kind === 'back') { + continue; + } + return refined; + } +} diff --git a/packages/cli/src/setup-databases.test.ts b/packages/cli/src/setup-databases.test.ts index c8505bd1..d72029d2 100644 --- a/packages/cli/src/setup-databases.test.ts +++ b/packages/cli/src/setup-databases.test.ts @@ -42,7 +42,7 @@ function makeIo() { type ScopePick = | 'back' | 'enable-all' - | { schemas: string[]; tables: string[] }; + | { schemas: string[]; tables: string[] | 'back' }; interface PickerStubs { pickDatabaseScope: KtxSetupDatabasesDeps['pickDatabaseScope']; @@ -58,15 +58,21 @@ function makePickerStubs(options: { scopes?: ScopePick[] } = {}): PickerStubs { scopeCalls.push(args); const next = queue.shift(); if (next === undefined || next === 'enable-all') { - const enabledTables = args.discovered.map((t) => `${t.schema}.${t.name}`); + const schemas = args.initialSchemas && args.initialSchemas.length > 0 ? [...args.initialSchemas] : [...args.schemas]; + const discovered = await args.listTablesForSchemas(schemas); + const enabledTables = discovered.map((t) => `${t.schema}.${t.name}`); const activeSchemas = args.supportsSchemaScope - ? Array.from(new Set(args.discovered.map((t) => t.schema))) + ? Array.from(new Set(discovered.map((t) => t.schema))) : []; return { kind: 'selected', activeSchemas, enabledTables }; } if (next === 'back') { return { kind: 'back' }; } + await args.listTablesForSchemas(next.schemas); + if (next.tables === 'back') { + return { kind: 'back' }; + } return { kind: 'selected', activeSchemas: args.supportsSchemaScope ? next.schemas : [], @@ -88,7 +94,19 @@ function makePromptAdapter(options: { const passwordValues = [...(options.passwordValues ?? [])]; return { multiselect: vi.fn(async () => multiselectValues.shift() ?? ['postgres']), + autocompleteMultiselect: vi.fn(async (options) => { + if (multiselectValues.length > 0) { + return multiselectValues.shift() ?? []; + } + if (options.initialValues && options.initialValues.length > 0) { + return options.initialValues; + } + return options.options.length > 0 ? options.options.map((option) => option.value) : ['back']; + }), select: vi.fn(async ({ message }) => { + if (message.startsWith('Save ') && message.includes(' or refine tables?')) { + return 'save'; + } if (message.includes('How much database context should KTX build?')) { const nextValue = selectValues[0]; return nextValue === 'fast' || nextValue === 'deep' || nextValue === 'back' @@ -915,7 +933,7 @@ describe('setup databases step', () => { placeholder: 'env:DATABASE_URL', initialValue: 'env:DATABASE_URL', }); - expect(listTables).toHaveBeenCalledWith(tempDir, 'warehouse', ['analytics', 'public']); + expect(listTables).toHaveBeenCalledWith(tempDir, 'warehouse', ['analytics']); expect(testConnection).toHaveBeenCalledWith(tempDir, 'warehouse', expect.anything()); expect(scanConnection).toHaveBeenCalledWith(tempDir, 'warehouse', expect.anything()); const config = parseKtxProjectConfig(await readFile(join(tempDir, 'ktx.yaml'), 'utf-8')); @@ -1105,7 +1123,7 @@ describe('setup databases step', () => { { schema: 'public', name: 'customers', kind: 'table' as const }, { schema: 'public', name: 'orders', kind: 'table' as const }, ]); - const pickers = makePickerStubs({ scopes: ['back'] }); + const pickers = makePickerStubs({ scopes: [{ schemas: ['public'], tables: 'back' }] }); const result = await runKtxSetupDatabasesStep( { projectDir: tempDir, inputMode: 'auto', skipDatabases: false, databaseSchemas: [] }, @@ -1469,7 +1487,7 @@ describe('setup databases step', () => { schemaSuggestion: { suggested: Set }; }; expect(args.schemaNoun).toBe('database'); - expect(args.discovered.map((table) => table.schema)).toEqual(['analytics', 'mart']); + expect(args.schemas).toEqual(['analytics', 'mart']); expect(scopedArgs.schemaSuggestion.suggested).toEqual(new Set(['analytics', 'mart'])); return { kind: 'selected' as const, activeSchemas: ['mart'], enabledTables: ['mart.orders'] }; }); @@ -1591,7 +1609,8 @@ describe('setup databases step', () => { connectionId: 'postgres-warehouse', schemaNoun: 'schema', schemaNounPlural: 'schemas', - defaultSchemas: ['orbit_analytics', 'orbit_raw'], + schemas: ['orbit_analytics', 'orbit_raw', 'public'], + schemaSuggestion: { excluded: new Set(), suggested: new Set() }, }); const config = parseKtxProjectConfig(await readFile(join(tempDir, 'ktx.yaml'), 'utf-8')); expect(config.connections['postgres-warehouse']).toMatchObject({ @@ -1600,6 +1619,41 @@ describe('setup databases step', () => { expect(io.stdout()).toContain('✓ orbit_analytics, orbit_raw'); }); + it('passes schemas and a lazy table callback to the scope picker instead of eager table discovery', async () => { + const listSchemas = vi.fn(async () => ['analytics', 'raw']); + const listTables = vi.fn(async (_projectDir: string, _connectionId: string, schemas?: string[]) => + (schemas ?? []).map((schema) => ({ schema, name: 'orders', kind: 'table' as const })), + ); + const pickDatabaseScope = vi.fn(async (args: PickDatabaseScopeArgs) => { + const lazyArgs = args as PickDatabaseScopeArgs & { + schemas: string[]; + listTablesForSchemas: (schemas: string[]) => Promise>; + }; + expect(lazyArgs.schemas).toEqual(['analytics', 'raw']); + expect(args).not.toHaveProperty('discovered'); + expect(listTables).not.toHaveBeenCalled(); + const tables = await lazyArgs.listTablesForSchemas(['analytics']); + expect(tables).toEqual([{ schema: 'analytics', name: 'orders', kind: 'table' }]); + return { kind: 'selected' as const, activeSchemas: ['analytics'], enabledTables: ['analytics.orders'] }; + }); + + await runKtxSetupDatabasesStep( + { projectDir: tempDir, inputMode: 'auto', databaseDrivers: ['postgres'], skipDatabases: false, databaseSchemas: [] }, + makeIo().io, + { + prompts: makePromptAdapter({ selectValues: ['url'], textValues: ['', 'env:DATABASE_URL'] }), + testConnection: vi.fn(async () => 0), + scanConnection: vi.fn(async () => 0), + listSchemas, + listTables, + pickDatabaseScope, + }, + ); + + expect(listTables).toHaveBeenCalledTimes(1); + expect(listTables).toHaveBeenCalledWith(tempDir, 'postgres-warehouse', ['analytics']); + }); + it('auto-selects all discovered Postgres schemas in non-interactive setup', async () => { const io = makeIo(); const prompts = makePromptAdapter({}); diff --git a/packages/cli/src/setup-databases.ts b/packages/cli/src/setup-databases.ts index 93eb6aa6..e26b6343 100644 --- a/packages/cli/src/setup-databases.ts +++ b/packages/cli/src/setup-databases.ts @@ -69,6 +69,14 @@ export interface KtxSetupDatabasesPromptAdapter { initialValues?: string[]; }): Promise; select(options: { message: string; options: KtxSetupPromptOption[] }): Promise; + autocompleteMultiselect(options: { + message: string; + options: KtxSetupPromptOption[]; + placeholder?: string; + required?: boolean; + maxItems?: number; + initialValues?: string[]; + }): Promise; text(options: { message: string; placeholder?: string; initialValue?: string }): Promise; password(options: { message: string }): Promise; cancel(message: string): void; @@ -156,19 +164,6 @@ function defaultSuggest(values: string[]): ScopeSuggestion { return { excluded, suggested }; } -function legacyDefaultSchemasForPicker( - schemas: string[], - suggestion: ScopeSuggestion, -): string[] { - const suggested = schemas.filter((schema) => suggestion.suggested.has(schema)); - if (suggested.length > 0) { - return suggested; - } - const visible = schemas.filter((schema) => !suggestion.excluded.has(schema)); - const nonPublic = visible.filter((schema) => schema !== 'public' && schema !== 'PUBLIC'); - return nonPublic.length > 0 ? nonPublic : visible; -} - const SCOPE_DISCOVERY_SPECS: Partial> = { postgres: { noun: 'schema', @@ -1413,6 +1408,7 @@ async function maybeConfigureDatabaseScope(input: { args: KtxSetupDatabasesArgs; deps: KtxSetupDatabasesDeps; io: KtxCliIo; + prompts: KtxSetupDatabasesPromptAdapter; forcePrompt?: boolean; }): Promise { const project = await loadKtxProject({ projectDir: input.projectDir }); @@ -1473,32 +1469,53 @@ async function maybeConfigureDatabaseScope(input: { }); } - writeSetupSection(input.io, 'Discovering tables', [ - `Connecting to ${input.connectionId}…`, - ]); + writeSetupSection(input.io, 'Discovering tables', [`Connecting to ${input.connectionId}…`]); - const schemasFilter = await (async (): Promise => { - if (cliSchemas.length > 0) return cliSchemas; - if (!spec) return []; - try { - return unique( - await (input.deps.listSchemas ?? defaultListSchemas)(input.projectDir, input.connectionId), - ); - } catch (error) { - const detail = error instanceof Error ? error.message : String(error); - input.io.stderr.write( - `Could not discover ${spec.promptLabel.toLowerCase()} for ${input.connectionId}; ${detail}\n`, - ); - return []; - } - })(); + const schemas = unique( + cliSchemas.length > 0 + ? cliSchemas + : await (async (): Promise => { + if (!spec) return []; + try { + return await (input.deps.listSchemas ?? defaultListSchemas)(input.projectDir, input.connectionId); + } catch (error) { + const detail = error instanceof Error ? error.message : String(error); + input.io.stderr.write( + `Could not discover ${spec.promptLabel.toLowerCase()} for ${input.connectionId}; ${detail}\n`, + ); + return []; + } + })(), + ); + if (spec && schemas.length === 0) { + return 'ready'; + } + const schemaSuggestion = + cliSchemas.length > 0 + ? { excluded: new Set(), suggested: new Set(cliSchemas) } + : spec?.suggest(schemas) ?? { excluded: new Set(), suggested: new Set() }; + const existingEnabled = + hasExistingTables && input.forcePrompt === true + ? (existingTables ?? []).filter((table): table is string => typeof table === 'string') + : []; - let discovered: KtxTableListEntry[]; + let pickResult: DatabaseScopePickResult; try { - discovered = await (input.deps.listTables ?? defaultListTables)( - input.projectDir, - input.connectionId, - schemasFilter.length > 0 ? schemasFilter : undefined, + pickResult = await (input.deps.pickDatabaseScope ?? defaultPickDatabaseScope)( + { + connectionId: input.connectionId, + schemaNoun: spec?.noun ?? 'schema', + schemaNounPlural: spec?.nounPlural ?? 'schemas', + schemas, + schemaSuggestion, + existing: { enabledTables: existingEnabled }, + supportsSchemaScope: spec !== undefined, + initialSchemas: cliSchemas.length > 0 ? cliSchemas : undefined, + prompts: input.prompts, + listTablesForSchemas: (selectedSchemas) => + (input.deps.listTables ?? defaultListTables)(input.projectDir, input.connectionId, selectedSchemas), + }, + input.io, ); } catch (error) { const detail = error instanceof Error ? error.message : String(error); @@ -1509,60 +1526,11 @@ async function maybeConfigureDatabaseScope(input: { ); return input.forcePrompt === true ? 'failed' : 'ready'; } - - if (discovered.length === 0) { - if (input.forcePrompt === true) { - input.io.stderr.write(`No tables discovered for ${input.connectionId}; edit was not saved.\n`); - } - return input.forcePrompt === true ? 'failed' : 'ready'; - } - - const allQualified = discovered.map((t) => `${t.schema}.${t.name}`); - const schemasInDiscovery = unique(discovered.map((t) => t.schema)); - - const defaultSchemas = (() => { - if (cliSchemas.length > 0) return cliSchemas; - if (!spec) return schemasInDiscovery; - const suggestion = spec.suggest(schemasInDiscovery); - return legacyDefaultSchemasForPicker(schemasInDiscovery, suggestion); - })(); - const schemaSuggestion = cliSchemas.length > 0 - ? { excluded: new Set(), suggested: new Set(cliSchemas) } - : spec?.suggest(schemasInDiscovery); - - const existingEnabled = - hasExistingTables && input.forcePrompt === true - ? (existingTables ?? []).filter( - (table): table is string => typeof table === 'string' && allQualified.includes(table), - ) - : []; - - let activeSchemas: string[]; - let enabledTables: string[]; - - if (discovered.length === 1) { - enabledTables = allQualified; - activeSchemas = spec ? schemasInDiscovery : []; - } else { - const pickResult = await (input.deps.pickDatabaseScope ?? defaultPickDatabaseScope)( - { - connectionId: input.connectionId, - schemaNoun: spec?.noun ?? 'schema', - schemaNounPlural: spec?.nounPlural ?? 'schemas', - discovered, - existing: { enabledTables: existingEnabled }, - defaultSchemas, - ...(schemaSuggestion ? { schemaSuggestion } : {}), - supportsSchemaScope: spec !== undefined, - }, - input.io, - ); - if (pickResult.kind === 'back') { - return 'back'; - } - enabledTables = pickResult.enabledTables; - activeSchemas = pickResult.activeSchemas; + if (pickResult.kind === 'back') { + return 'back'; } + const enabledTables = pickResult.enabledTables; + const activeSchemas = pickResult.activeSchemas; if (spec) { await writeScopeConfig({ @@ -1588,7 +1556,7 @@ async function maybeConfigureDatabaseScope(input: { ]); } writeSetupSection(input.io, `Tables enabled for ${input.connectionId}`, [ - `✓ ${enabledTables.length}/${discovered.length} tables enabled`, + `✓ ${enabledTables.length} tables enabled`, ]); return 'ready'; }