@kaelio/ktx 0.7.0 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/assets/python/{kaelio_ktx-0.7.0-py3-none-any.whl → kaelio_ktx-0.9.0-py3-none-any.whl} +0 -0
- package/assets/python/manifest.json +4 -4
- package/dist/.tsbuildinfo +1 -1
- package/dist/cli-program.js +7 -0
- package/dist/cli-runtime.js +50 -3
- package/dist/command-schemas.d.ts +1 -1
- package/dist/command-tree.js +5 -1
- package/dist/commands/completion-commands.d.ts +3 -0
- package/dist/commands/completion-commands.js +38 -0
- package/dist/commands/ingest-commands.js +0 -4
- package/dist/commands/knowledge-commands.js +15 -2
- package/dist/commands/setup-commands.js +3 -3
- package/dist/commands/sl-commands.js +19 -7
- package/dist/completion/complete-engine.d.ts +19 -0
- package/dist/completion/complete-engine.js +128 -0
- package/dist/completion/completion-scripts.d.ts +1 -0
- package/dist/completion/completion-scripts.js +36 -0
- package/dist/completion/dynamic-candidates.d.ts +6 -0
- package/dist/completion/dynamic-candidates.js +98 -0
- package/dist/connection-drivers.d.ts +3 -0
- package/dist/connection-drivers.js +17 -0
- package/dist/connection-recovery.d.ts +34 -0
- package/dist/connection-recovery.js +82 -0
- package/dist/connection.js +3 -1
- package/dist/context/ingest/adapters/historic-sql/bigquery-query-history-reader.js +71 -20
- package/dist/context/ingest/adapters/historic-sql/chunk-unified.js +2 -1
- package/dist/context/ingest/adapters/historic-sql/connection-dialect.d.ts +9 -0
- package/dist/context/ingest/adapters/historic-sql/connection-dialect.js +15 -4
- package/dist/context/ingest/adapters/historic-sql/pattern-inputs.js +8 -2
- package/dist/context/ingest/adapters/historic-sql/query-history-filter-picker.d.ts +29 -0
- package/dist/context/ingest/adapters/historic-sql/query-history-filter-picker.js +190 -0
- package/dist/context/ingest/adapters/historic-sql/scope-floor.d.ts +18 -0
- package/dist/context/ingest/adapters/historic-sql/scope-floor.js +229 -0
- package/dist/context/ingest/adapters/historic-sql/scope-membership.d.ts +8 -0
- package/dist/context/ingest/adapters/historic-sql/scope-membership.js +29 -0
- package/dist/context/ingest/adapters/historic-sql/snowflake-query-history-reader.js +68 -19
- package/dist/context/ingest/adapters/historic-sql/stage-unified.js +57 -50
- package/dist/context/ingest/adapters/historic-sql/types.d.ts +36 -3
- package/dist/context/ingest/adapters/historic-sql/types.js +14 -2
- package/dist/context/ingest/context-evidence/sqlite-context-evidence-store.d.ts +1 -1
- package/dist/context/ingest/ingest-bundle.runner.d.ts +8 -0
- package/dist/context/ingest/ingest-bundle.runner.js +72 -15
- package/dist/context/ingest/ingest-profile.d.ts +102 -0
- package/dist/context/ingest/ingest-profile.js +306 -0
- package/dist/context/ingest/isolated-diff/patch-integrator.js +75 -5
- package/dist/context/ingest/isolated-diff/work-unit-executor.js +25 -2
- package/dist/context/ingest/local-adapters.js +21 -4
- package/dist/context/ingest/local-bundle-runtime.js +4 -2
- package/dist/context/ingest/local-ingest.d.ts +1 -1
- package/dist/context/ingest/local-ingest.js +6 -4
- package/dist/context/ingest/memory-flow/events.js +2 -1
- package/dist/context/ingest/ports.d.ts +2 -0
- package/dist/context/ingest/reports.d.ts +3 -0
- package/dist/context/ingest/reports.js +10 -0
- package/dist/context/ingest/stages/stage-3-work-units.d.ts +3 -1
- package/dist/context/ingest/stages/stage-3-work-units.js +2 -0
- package/dist/context/ingest/stages/stage-4-reconciliation.d.ts +2 -1
- package/dist/context/ingest/stages/stage-4-reconciliation.js +1 -1
- package/dist/context/ingest/tools/tool-call-logger.d.ts +6 -0
- package/dist/context/ingest/tools/tool-call-logger.js +36 -1
- package/dist/context/llm/ai-sdk-runtime.js +32 -3
- package/dist/context/llm/claude-code-runtime.js +35 -2
- package/dist/context/llm/codex-exec-events.d.ts +20 -0
- package/dist/context/llm/codex-exec-events.js +155 -0
- package/dist/context/llm/codex-isolation.d.ts +3 -0
- package/dist/context/llm/codex-isolation.js +5 -0
- package/dist/context/llm/codex-mcp-runtime-server.d.ts +24 -0
- package/dist/context/llm/codex-mcp-runtime-server.js +51 -0
- package/dist/context/llm/codex-models.d.ts +2 -0
- package/dist/context/llm/codex-models.js +17 -0
- package/dist/context/llm/codex-runtime-config.d.ts +16 -0
- package/dist/context/llm/codex-runtime-config.js +19 -0
- package/dist/context/llm/codex-runtime.d.ts +37 -0
- package/dist/context/llm/codex-runtime.js +304 -0
- package/dist/context/llm/codex-sdk-runner.d.ts +21 -0
- package/dist/context/llm/codex-sdk-runner.js +63 -0
- package/dist/context/llm/local-config.d.ts +2 -0
- package/dist/context/llm/local-config.js +12 -1
- package/dist/context/llm/runtime-port.d.ts +25 -0
- package/dist/context/mcp/context-tools.d.ts +2 -1
- package/dist/context/mcp/context-tools.js +82 -15
- package/dist/context/mcp/server.js +4 -0
- package/dist/context/mcp/types.d.ts +15 -1
- package/dist/context/project/config.d.ts +3 -0
- package/dist/context/project/config.js +6 -2
- package/dist/context/project/driver-schemas.js +1 -1
- package/dist/context/search/discover.js +4 -3
- package/dist/context/sl/local-sl.d.ts +15 -0
- package/dist/context/sl/local-sl.js +30 -0
- package/dist/context/sql-analysis/http-sql-analysis-port.js +32 -2
- package/dist/context/sql-analysis/ports.d.ts +12 -2
- package/dist/context/tools/context-candidate-mark.tool.d.ts +2 -2
- package/dist/context/wiki/local-knowledge.d.ts +10 -0
- package/dist/context/wiki/local-knowledge.js +22 -0
- package/dist/context-build-view.d.ts +0 -3
- package/dist/context-build-view.js +5 -39
- package/dist/ingest.js +7 -10
- package/dist/io/buffered-command-io.d.ts +11 -0
- package/dist/io/buffered-command-io.js +28 -0
- package/dist/knowledge.d.ts +5 -0
- package/dist/knowledge.js +10 -1
- package/dist/llm/types.d.ts +1 -1
- package/dist/local-adapters.d.ts +10 -2
- package/dist/local-adapters.js +19 -3
- package/dist/next-steps.js +1 -2
- package/dist/progress-port-adapter.d.ts +6 -0
- package/dist/progress-port-adapter.js +18 -0
- package/dist/public-ingest-copy.js +1 -1
- package/dist/public-ingest.d.ts +20 -8
- package/dist/public-ingest.js +198 -61
- package/dist/scan.js +3 -1
- package/dist/setup-context.d.ts +2 -0
- package/dist/setup-context.js +138 -64
- package/dist/setup-databases.d.ts +17 -1
- package/dist/setup-databases.js +366 -326
- package/dist/setup-models.d.ts +10 -1
- package/dist/setup-models.js +90 -2
- package/dist/setup-ready-menu.d.ts +16 -2
- package/dist/setup-ready-menu.js +37 -5
- package/dist/setup-sources.js +141 -33
- package/dist/setup.js +24 -12
- package/dist/skills/analytics/SKILL.md +6 -1
- package/dist/sl.d.ts +6 -1
- package/dist/sl.js +32 -8
- package/dist/status-project.d.ts +11 -0
- package/dist/status-project.js +50 -1
- package/dist/telemetry/command-hook.d.ts +1 -0
- package/dist/telemetry/command-hook.js +3 -1
- package/dist/telemetry/emitter.js +1 -1
- package/dist/telemetry/events.d.ts +15 -9
- package/dist/telemetry/events.js +17 -5
- package/dist/telemetry/identity.d.ts +1 -2
- package/dist/telemetry/identity.js +13 -10
- package/dist/telemetry/index.d.ts +13 -1
- package/dist/telemetry/index.js +18 -3
- package/dist/telemetry/scrubber.d.ts +10 -0
- package/dist/telemetry/scrubber.js +20 -0
- package/package.json +20 -19
- package/dist/ingest-depth.d.ts +0 -8
- package/dist/ingest-depth.js +0 -56
- package/dist/setup-database-context-depth.d.ts +0 -23
- package/dist/setup-database-context-depth.js +0 -84
package/dist/setup-databases.js
CHANGED
|
@@ -4,23 +4,30 @@ import { delimiter, dirname, join } from 'node:path';
|
|
|
4
4
|
import { fileURLToPath } from 'node:url';
|
|
5
5
|
import { promisify } from 'node:util';
|
|
6
6
|
import { getDriverRegistration } from './context/connections/drivers.js';
|
|
7
|
+
import { createLocalKtxLlmRuntimeFromConfig } from './context/llm/local-config.js';
|
|
7
8
|
import { queryHistoryDialectForConnection } from './context/ingest/adapters/historic-sql/connection-dialect.js';
|
|
9
|
+
import { proposeQueryHistoryServiceAccountFilters, } from './context/ingest/adapters/historic-sql/query-history-filter-picker.js';
|
|
10
|
+
import { resolveQueryHistoryScopeFloor } from './context/ingest/adapters/historic-sql/scope-floor.js';
|
|
8
11
|
import { runHistoricSqlReadinessProbe, } from './context/ingest/historic-sql-probes.js';
|
|
9
12
|
import { serializeKtxProjectConfig } from './context/project/config.js';
|
|
10
13
|
import { loadKtxProject } from './context/project/project.js';
|
|
11
14
|
import { markKtxSetupStateStepComplete, setKtxSetupDatabaseConnectionIds } from './context/project/setup-config.js';
|
|
15
|
+
import { getKtxCliPackageInfo } from './cli-runtime.js';
|
|
12
16
|
import { errorMessage, flushPrefixedBufferedCommandOutput, writePrefixedLines, } from './clack.js';
|
|
13
17
|
import { runKtxConnection } from './connection.js';
|
|
18
|
+
import { createBufferedCommandIo } from './io/buffered-command-io.js';
|
|
19
|
+
import { runConnectionSetupWithRecovery, } from './connection-recovery.js';
|
|
14
20
|
import { pickDatabaseScope as defaultPickDatabaseScope, } from './database-tree-picker.js';
|
|
15
21
|
import { withMultiselectNavigation, withTextInputNavigation } from './prompt-navigation.js';
|
|
22
|
+
import { createKtxCliHistoricSqlRuntime } from './local-adapters.js';
|
|
23
|
+
import { queryHistoryPullConfig } from './public-ingest.js';
|
|
16
24
|
import { runKtxScan } from './scan.js';
|
|
17
|
-
import { applySetupDatabaseContextDepth } from './setup-database-context-depth.js';
|
|
18
25
|
import { writeProjectLocalSecretReference } from './setup-secrets.js';
|
|
19
26
|
import { isDemoConnection } from './telemetry/demo-detect.js';
|
|
20
27
|
import { emitTelemetryEvent } from './telemetry/index.js';
|
|
21
28
|
import { createKtxSetupPromptAdapter, createKtxSetupUiAdapter, } from './setup-prompts.js';
|
|
22
29
|
const HISTORIC_SQL_WORK_UNIT_MAX_CONCURRENCY = 6;
|
|
23
|
-
const
|
|
30
|
+
const KTX_DEMO_START_URL = 'https://www.kaelio.com/start';
|
|
24
31
|
const execFileAsync = promisify(execFileCallback);
|
|
25
32
|
const DRIVER_OPTIONS = [
|
|
26
33
|
{ value: 'postgres', label: 'PostgreSQL' },
|
|
@@ -672,10 +679,13 @@ async function maybeApplyHistoricSqlConfig(input) {
|
|
|
672
679
|
if (!enabled) {
|
|
673
680
|
return withQueryHistoryConfig(input.connection, { ...existing, enabled: false });
|
|
674
681
|
}
|
|
682
|
+
const existingFilters = existing.filters && typeof existing.filters === 'object' && !Array.isArray(existing.filters)
|
|
683
|
+
? existing.filters
|
|
684
|
+
: {};
|
|
675
685
|
const common = {
|
|
676
686
|
...existing,
|
|
677
687
|
enabled: true,
|
|
678
|
-
filters: historicSqlFiltersForSetup(input.args.queryHistoryServiceAccountPatterns),
|
|
688
|
+
filters: historicSqlFiltersForSetup(input.args.queryHistoryServiceAccountPatterns, existingFilters),
|
|
679
689
|
};
|
|
680
690
|
if (dialect === 'postgres') {
|
|
681
691
|
return withQueryHistoryConfig(input.connection, {
|
|
@@ -689,9 +699,10 @@ async function maybeApplyHistoricSqlConfig(input) {
|
|
|
689
699
|
redactionPatterns: input.args.queryHistoryRedactionPatterns ?? [],
|
|
690
700
|
});
|
|
691
701
|
}
|
|
692
|
-
function historicSqlFiltersForSetup(patterns) {
|
|
702
|
+
function historicSqlFiltersForSetup(patterns, existingFilters = {}) {
|
|
693
703
|
const serviceAccountPatterns = patterns ?? [];
|
|
694
704
|
return {
|
|
705
|
+
...existingFilters,
|
|
695
706
|
dropTrivialProbes: true,
|
|
696
707
|
...(serviceAccountPatterns.length > 0
|
|
697
708
|
? {
|
|
@@ -716,29 +727,6 @@ async function defaultScanConnection(projectDir, connectionId, io) {
|
|
|
716
727
|
dryRun: false,
|
|
717
728
|
}, io);
|
|
718
729
|
}
|
|
719
|
-
function createBufferedCommandIo() {
|
|
720
|
-
let stdout = '';
|
|
721
|
-
let stderr = '';
|
|
722
|
-
return {
|
|
723
|
-
stdout: {
|
|
724
|
-
isTTY: false,
|
|
725
|
-
write(chunk) {
|
|
726
|
-
stdout += chunk;
|
|
727
|
-
},
|
|
728
|
-
},
|
|
729
|
-
stderr: {
|
|
730
|
-
write(chunk) {
|
|
731
|
-
stderr += chunk;
|
|
732
|
-
},
|
|
733
|
-
},
|
|
734
|
-
stdoutText() {
|
|
735
|
-
return stdout;
|
|
736
|
-
},
|
|
737
|
-
stderrText() {
|
|
738
|
-
return stderr;
|
|
739
|
-
},
|
|
740
|
-
};
|
|
741
|
-
}
|
|
742
730
|
function envWithCurrentNodeFirst(env = process.env) {
|
|
743
731
|
return {
|
|
744
732
|
...env,
|
|
@@ -890,6 +878,27 @@ async function disableConnectionQueryHistory(projectDir, connectionId) {
|
|
|
890
878
|
connection: withQueryHistoryConfig(connection, { ...existing, enabled: false }),
|
|
891
879
|
});
|
|
892
880
|
}
|
|
881
|
+
function okValidateResult() {
|
|
882
|
+
return { status: 'ok' };
|
|
883
|
+
}
|
|
884
|
+
function backValidateResult() {
|
|
885
|
+
return { status: 'back' };
|
|
886
|
+
}
|
|
887
|
+
function failedValidateResult() {
|
|
888
|
+
return { status: 'failed' };
|
|
889
|
+
}
|
|
890
|
+
function queryHistoryUnavailableResult(projectDir, connectionId) {
|
|
891
|
+
return {
|
|
892
|
+
status: 'failed',
|
|
893
|
+
extraActions: [
|
|
894
|
+
{
|
|
895
|
+
value: 'disable-query-history',
|
|
896
|
+
label: 'Disable query history and retry',
|
|
897
|
+
run: () => disableConnectionQueryHistory(projectDir, connectionId),
|
|
898
|
+
},
|
|
899
|
+
],
|
|
900
|
+
};
|
|
901
|
+
}
|
|
893
902
|
async function createConnectionConfigRollback(projectDir, connectionId) {
|
|
894
903
|
const project = await loadKtxProject({ projectDir });
|
|
895
904
|
const previousConnection = project.config.connections[connectionId];
|
|
@@ -984,14 +993,14 @@ async function maybeConfigureDatabaseScope(input) {
|
|
|
984
993
|
const connection = project.config.connections[input.connectionId];
|
|
985
994
|
const driver = normalizeDriver(connection?.driver);
|
|
986
995
|
if (!driver || driver === 'sqlite')
|
|
987
|
-
return
|
|
996
|
+
return okValidateResult();
|
|
988
997
|
const spec = SCOPE_DISCOVERY_SPECS[driver];
|
|
989
998
|
const existingTables = connection?.enabled_tables;
|
|
990
999
|
const hasExistingTables = Array.isArray(existingTables) && existingTables.length > 0;
|
|
991
1000
|
const existingScope = spec ? configuredScopeValues(connection, spec) : [];
|
|
992
1001
|
const hasExistingScope = !spec || existingScope.length > 0;
|
|
993
1002
|
if (hasExistingTables && hasExistingScope && input.forcePrompt !== true) {
|
|
994
|
-
return
|
|
1003
|
+
return okValidateResult();
|
|
995
1004
|
}
|
|
996
1005
|
const cliSchemas = input.args.databaseSchemas;
|
|
997
1006
|
if (input.args.inputMode === 'disabled') {
|
|
@@ -1004,7 +1013,7 @@ async function maybeConfigureDatabaseScope(input) {
|
|
|
1004
1013
|
catch (error) {
|
|
1005
1014
|
const detail = error instanceof Error ? error.message : String(error);
|
|
1006
1015
|
input.io.stderr.write(`Could not discover ${spec.promptLabel.toLowerCase()} for ${input.connectionId}; ${detail}\n`);
|
|
1007
|
-
return
|
|
1016
|
+
return okValidateResult();
|
|
1008
1017
|
}
|
|
1009
1018
|
}
|
|
1010
1019
|
if (scopeToWrite.length > 0) {
|
|
@@ -1020,7 +1029,7 @@ async function maybeConfigureDatabaseScope(input) {
|
|
|
1020
1029
|
]);
|
|
1021
1030
|
}
|
|
1022
1031
|
}
|
|
1023
|
-
return
|
|
1032
|
+
return okValidateResult();
|
|
1024
1033
|
}
|
|
1025
1034
|
if (spec && cliSchemas.length > 0) {
|
|
1026
1035
|
await writeScopeConfig({
|
|
@@ -1051,7 +1060,7 @@ async function maybeConfigureDatabaseScope(input) {
|
|
|
1051
1060
|
spec,
|
|
1052
1061
|
});
|
|
1053
1062
|
if (typed === undefined)
|
|
1054
|
-
return
|
|
1063
|
+
return backValidateResult();
|
|
1055
1064
|
effectiveCliSchemas = typed;
|
|
1056
1065
|
listedSchemas = typed;
|
|
1057
1066
|
if (typed.length > 0) {
|
|
@@ -1066,7 +1075,7 @@ async function maybeConfigureDatabaseScope(input) {
|
|
|
1066
1075
|
}
|
|
1067
1076
|
const schemas = unique(listedSchemas);
|
|
1068
1077
|
if (spec && schemas.length === 0) {
|
|
1069
|
-
return
|
|
1078
|
+
return okValidateResult();
|
|
1070
1079
|
}
|
|
1071
1080
|
const schemaSuggestion = effectiveCliSchemas.length > 0
|
|
1072
1081
|
? { excluded: new Set(), suggested: new Set(effectiveCliSchemas) }
|
|
@@ -1094,10 +1103,10 @@ async function maybeConfigureDatabaseScope(input) {
|
|
|
1094
1103
|
writePrefixedLines((chunk) => input.io.stderr.write(chunk), input.forcePrompt === true
|
|
1095
1104
|
? `Could not discover tables for ${input.connectionId}; edit was not saved. ${detail}`
|
|
1096
1105
|
: `Could not discover tables for ${input.connectionId}; continuing without table filter. ${detail}`);
|
|
1097
|
-
return input.forcePrompt === true ?
|
|
1106
|
+
return input.forcePrompt === true ? failedValidateResult() : okValidateResult();
|
|
1098
1107
|
}
|
|
1099
1108
|
if (pickResult.kind === 'back') {
|
|
1100
|
-
return
|
|
1109
|
+
return backValidateResult();
|
|
1101
1110
|
}
|
|
1102
1111
|
const enabledTables = pickResult.enabledTables;
|
|
1103
1112
|
const activeSchemas = pickResult.activeSchemas;
|
|
@@ -1112,7 +1121,7 @@ async function maybeConfigureDatabaseScope(input) {
|
|
|
1112
1121
|
const refreshedProject = await loadKtxProject({ projectDir: input.projectDir });
|
|
1113
1122
|
const currentConnection = refreshedProject.config.connections[input.connectionId];
|
|
1114
1123
|
if (!currentConnection)
|
|
1115
|
-
return
|
|
1124
|
+
return okValidateResult();
|
|
1116
1125
|
await writeConnectionConfig({
|
|
1117
1126
|
projectDir: input.projectDir,
|
|
1118
1127
|
connectionId: input.connectionId,
|
|
@@ -1128,7 +1137,7 @@ async function maybeConfigureDatabaseScope(input) {
|
|
|
1128
1137
|
writeSetupSection(input.io, `Tables enabled for ${input.connectionId}`, [
|
|
1129
1138
|
`✓ ${enabledTables.length} tables enabled`,
|
|
1130
1139
|
]);
|
|
1131
|
-
return
|
|
1140
|
+
return okValidateResult();
|
|
1132
1141
|
}
|
|
1133
1142
|
async function ensureHistoricSqlIngestDefaults(projectDir) {
|
|
1134
1143
|
const project = await loadKtxProject({ projectDir });
|
|
@@ -1189,6 +1198,158 @@ async function maybeRunHistoricSqlSetupProbe(input) {
|
|
|
1189
1198
|
}
|
|
1190
1199
|
return result.ok;
|
|
1191
1200
|
}
|
|
1201
|
+
function hasServiceAccountsBlock(connection) {
|
|
1202
|
+
const queryHistory = queryHistoryConfigRecord(connection);
|
|
1203
|
+
const filters = queryHistory?.filters;
|
|
1204
|
+
if (!filters || typeof filters !== 'object' || Array.isArray(filters)) {
|
|
1205
|
+
return false;
|
|
1206
|
+
}
|
|
1207
|
+
return 'serviceAccounts' in filters;
|
|
1208
|
+
}
|
|
1209
|
+
function printQueryHistoryFilterProposal(io, proposal) {
|
|
1210
|
+
if (proposal.excludedRoles.length === 0) {
|
|
1211
|
+
if (proposal.skipped?.reason === 'no-llm') {
|
|
1212
|
+
io.stdout.write('│ Query-history filter picker skipped: no LLM is configured.\n');
|
|
1213
|
+
}
|
|
1214
|
+
else if (proposal.skipped?.reason === 'no-daemon') {
|
|
1215
|
+
io.stdout.write('│ Query-history filter picker skipped: SQL analysis is unavailable.\n');
|
|
1216
|
+
}
|
|
1217
|
+
else if (proposal.skipped?.reason === 'no-in-scope-history') {
|
|
1218
|
+
io.stdout.write('│ Query-history filter picker found no in-scope service-account exclusions.\n');
|
|
1219
|
+
}
|
|
1220
|
+
for (const warning of proposal.warnings) {
|
|
1221
|
+
io.stdout.write(`│ ! ${warning}\n`);
|
|
1222
|
+
}
|
|
1223
|
+
return;
|
|
1224
|
+
}
|
|
1225
|
+
io.stdout.write('│ Proposed query-history service-account filters:\n');
|
|
1226
|
+
for (const excluded of proposal.excludedRoles) {
|
|
1227
|
+
io.stdout.write(`│ - ${excluded.role}: ${excluded.reason}\n`);
|
|
1228
|
+
}
|
|
1229
|
+
}
|
|
1230
|
+
async function shouldApplyQueryHistoryFilterProposal(input) {
|
|
1231
|
+
if (input.proposal.excludedRoles.length === 0 || input.proposal.skipped?.reason === 'user-block-present') {
|
|
1232
|
+
return false;
|
|
1233
|
+
}
|
|
1234
|
+
if (input.args.yes === true || input.args.inputMode === 'disabled') {
|
|
1235
|
+
return true;
|
|
1236
|
+
}
|
|
1237
|
+
const choice = await input.prompts.select({
|
|
1238
|
+
message: `Apply ${input.proposal.excludedRoles.length} derived query-history service-account exclusion${input.proposal.excludedRoles.length === 1 ? '' : 's'}?`,
|
|
1239
|
+
options: [
|
|
1240
|
+
{ value: 'apply', label: 'Apply derived filters (recommended)' },
|
|
1241
|
+
{ value: 'skip', label: 'Leave query history filters unchanged' },
|
|
1242
|
+
],
|
|
1243
|
+
});
|
|
1244
|
+
return choice === 'apply';
|
|
1245
|
+
}
|
|
1246
|
+
function createSetupQueryHistoryLlmRuntime(input) {
|
|
1247
|
+
try {
|
|
1248
|
+
return (input.deps.createQueryHistoryLlmRuntime?.(input.projectDir, input.project) ??
|
|
1249
|
+
createLocalKtxLlmRuntimeFromConfig(input.project.config.llm, {
|
|
1250
|
+
projectDir: input.projectDir,
|
|
1251
|
+
}));
|
|
1252
|
+
}
|
|
1253
|
+
catch {
|
|
1254
|
+
return null;
|
|
1255
|
+
}
|
|
1256
|
+
}
|
|
1257
|
+
/** @internal */
|
|
1258
|
+
export function managedDaemonOptionsForSetupQueryHistoryPicker(input) {
|
|
1259
|
+
return {
|
|
1260
|
+
cliVersion: input.args.cliVersion ?? getKtxCliPackageInfo().version,
|
|
1261
|
+
projectDir: input.projectDir,
|
|
1262
|
+
installPolicy: input.args.runtimeInstallPolicy ?? (input.args.inputMode === 'disabled' ? 'never' : 'prompt'),
|
|
1263
|
+
io: input.io,
|
|
1264
|
+
};
|
|
1265
|
+
}
|
|
1266
|
+
async function maybeProposeQueryHistoryFilters(input) {
|
|
1267
|
+
const project = await loadKtxProject({ projectDir: input.projectDir });
|
|
1268
|
+
const connection = project.config.connections[input.connectionId];
|
|
1269
|
+
const queryHistory = queryHistoryConfigRecord(connection);
|
|
1270
|
+
if (!connection || queryHistory?.enabled !== true) {
|
|
1271
|
+
return;
|
|
1272
|
+
}
|
|
1273
|
+
const dialect = queryHistoryDialectForConnection(connection);
|
|
1274
|
+
if (!dialect) {
|
|
1275
|
+
return;
|
|
1276
|
+
}
|
|
1277
|
+
const picker = input.deps.queryHistoryFilterPicker ?? proposeQueryHistoryServiceAccountFilters;
|
|
1278
|
+
const llmRuntime = createSetupQueryHistoryLlmRuntime({
|
|
1279
|
+
projectDir: input.projectDir,
|
|
1280
|
+
project,
|
|
1281
|
+
deps: input.deps,
|
|
1282
|
+
});
|
|
1283
|
+
if (!llmRuntime && !input.deps.queryHistoryFilterPicker) {
|
|
1284
|
+
printQueryHistoryFilterProposal(input.io, {
|
|
1285
|
+
excludedRoles: [],
|
|
1286
|
+
consideredRoleCount: 0,
|
|
1287
|
+
skipped: { reason: 'no-llm' },
|
|
1288
|
+
warnings: [],
|
|
1289
|
+
});
|
|
1290
|
+
return;
|
|
1291
|
+
}
|
|
1292
|
+
const runtime = createKtxCliHistoricSqlRuntime(project, input.connectionId, {
|
|
1293
|
+
managedDaemon: managedDaemonOptionsForSetupQueryHistoryPicker({
|
|
1294
|
+
projectDir: input.projectDir,
|
|
1295
|
+
args: input.args,
|
|
1296
|
+
io: input.io,
|
|
1297
|
+
}),
|
|
1298
|
+
});
|
|
1299
|
+
if (!runtime) {
|
|
1300
|
+
return;
|
|
1301
|
+
}
|
|
1302
|
+
const userServiceAccountsPresent = hasServiceAccountsBlock(connection);
|
|
1303
|
+
const scopeFloor = await resolveQueryHistoryScopeFloor({
|
|
1304
|
+
projectDir: input.projectDir,
|
|
1305
|
+
connectionId: input.connectionId,
|
|
1306
|
+
driver: String(connection.driver ?? ''),
|
|
1307
|
+
connection: connection,
|
|
1308
|
+
storedQueryHistory: queryHistory,
|
|
1309
|
+
});
|
|
1310
|
+
const pullConfig = queryHistoryPullConfig({
|
|
1311
|
+
stored: queryHistory,
|
|
1312
|
+
dialect,
|
|
1313
|
+
enabledTables: scopeFloor.enabledTables,
|
|
1314
|
+
enabledSchemas: scopeFloor.enabledSchemas,
|
|
1315
|
+
modeledTableCatalog: scopeFloor.modeledTableCatalog,
|
|
1316
|
+
scopeFloorWarnings: scopeFloor.warnings,
|
|
1317
|
+
});
|
|
1318
|
+
const proposal = await picker({
|
|
1319
|
+
connectionId: input.connectionId,
|
|
1320
|
+
dialect,
|
|
1321
|
+
queryClient: runtime.queryClient,
|
|
1322
|
+
reader: runtime.reader,
|
|
1323
|
+
sqlAnalysis: runtime.sqlAnalysis,
|
|
1324
|
+
llmRuntime,
|
|
1325
|
+
pullConfig,
|
|
1326
|
+
userServiceAccountsPresent,
|
|
1327
|
+
});
|
|
1328
|
+
printQueryHistoryFilterProposal(input.io, proposal);
|
|
1329
|
+
if (proposal.skipped?.reason === 'user-block-present') {
|
|
1330
|
+
input.io.stdout.write('│ Existing query-history service-account filters left unchanged.\n');
|
|
1331
|
+
return;
|
|
1332
|
+
}
|
|
1333
|
+
if (!(await shouldApplyQueryHistoryFilterProposal({ args: input.args, prompts: input.prompts, proposal }))) {
|
|
1334
|
+
return;
|
|
1335
|
+
}
|
|
1336
|
+
await writeConnectionConfig({
|
|
1337
|
+
projectDir: input.projectDir,
|
|
1338
|
+
connectionId: input.connectionId,
|
|
1339
|
+
connection: withQueryHistoryConfig(connection, {
|
|
1340
|
+
...queryHistory,
|
|
1341
|
+
filters: {
|
|
1342
|
+
...(queryHistory.filters && typeof queryHistory.filters === 'object' && !Array.isArray(queryHistory.filters)
|
|
1343
|
+
? queryHistory.filters
|
|
1344
|
+
: {}),
|
|
1345
|
+
serviceAccounts: {
|
|
1346
|
+
mode: 'exclude',
|
|
1347
|
+
patterns: proposal.excludedRoles.map((role) => role.pattern),
|
|
1348
|
+
},
|
|
1349
|
+
},
|
|
1350
|
+
}),
|
|
1351
|
+
});
|
|
1352
|
+
}
|
|
1192
1353
|
async function applyHistoricSqlConfigToExistingConnection(input) {
|
|
1193
1354
|
if (input.args.inputMode === 'disabled' &&
|
|
1194
1355
|
input.args.enableQueryHistory !== true &&
|
|
@@ -1209,39 +1370,10 @@ async function applyHistoricSqlConfigToExistingConnection(input) {
|
|
|
1209
1370
|
});
|
|
1210
1371
|
if (withHistoricSql === 'back')
|
|
1211
1372
|
return 'back';
|
|
1212
|
-
const withContextDepth = await maybeApplyContextDepthConfig({
|
|
1213
|
-
projectDir: input.projectDir,
|
|
1214
|
-
connectionId: input.connectionId,
|
|
1215
|
-
connection: withHistoricSql,
|
|
1216
|
-
args: input.args,
|
|
1217
|
-
prompts: input.prompts,
|
|
1218
|
-
});
|
|
1219
|
-
if (withContextDepth === 'back')
|
|
1220
|
-
return 'back';
|
|
1221
1373
|
await writeConnectionConfig({
|
|
1222
1374
|
projectDir: input.projectDir,
|
|
1223
1375
|
connectionId: input.connectionId,
|
|
1224
|
-
connection:
|
|
1225
|
-
});
|
|
1226
|
-
}
|
|
1227
|
-
async function maybeApplyContextDepthConfig(input) {
|
|
1228
|
-
const project = await loadKtxProject({ projectDir: input.projectDir });
|
|
1229
|
-
return await applySetupDatabaseContextDepth({
|
|
1230
|
-
project: {
|
|
1231
|
-
...project,
|
|
1232
|
-
config: {
|
|
1233
|
-
...project.config,
|
|
1234
|
-
connections: {
|
|
1235
|
-
...project.config.connections,
|
|
1236
|
-
[input.connectionId]: input.connection,
|
|
1237
|
-
},
|
|
1238
|
-
},
|
|
1239
|
-
},
|
|
1240
|
-
connection: input.connection,
|
|
1241
|
-
args: {
|
|
1242
|
-
inputMode: input.args.inputMode === 'disabled' || input.args.databaseUrl ? 'disabled' : input.args.inputMode,
|
|
1243
|
-
},
|
|
1244
|
-
prompts: input.prompts,
|
|
1376
|
+
connection: withHistoricSql,
|
|
1245
1377
|
});
|
|
1246
1378
|
}
|
|
1247
1379
|
async function validateAndScanConnection(input) {
|
|
@@ -1255,7 +1387,7 @@ async function validateAndScanConnection(input) {
|
|
|
1255
1387
|
if (testCode !== 0) {
|
|
1256
1388
|
flushPrefixedBufferedCommandOutput(input.io, testIo);
|
|
1257
1389
|
writePrefixedLines((chunk) => input.io.stderr.write(chunk), `Connection test failed for ${input.connectionId}.`);
|
|
1258
|
-
return
|
|
1390
|
+
return failedValidateResult();
|
|
1259
1391
|
}
|
|
1260
1392
|
const testOutput = testIo.stdoutText();
|
|
1261
1393
|
const outputDriver = normalizeDriver(readOutputValue(testOutput, 'Driver'));
|
|
@@ -1263,7 +1395,7 @@ async function validateAndScanConnection(input) {
|
|
|
1263
1395
|
const testLines = ['✓ Connection test passed', `Driver: ${driverDisplay}`];
|
|
1264
1396
|
writeSetupSection(input.io, `Testing ${input.connectionId}`, testLines);
|
|
1265
1397
|
const scopeStatus = await maybeConfigureDatabaseScope({ ...input, forcePrompt: input.forceScopeAndTables });
|
|
1266
|
-
if (scopeStatus !== '
|
|
1398
|
+
if (scopeStatus.status !== 'ok') {
|
|
1267
1399
|
return scopeStatus;
|
|
1268
1400
|
}
|
|
1269
1401
|
const queryHistoryAvailable = await maybeRunHistoricSqlSetupProbe({
|
|
@@ -1273,7 +1405,7 @@ async function validateAndScanConnection(input) {
|
|
|
1273
1405
|
deps: input.deps,
|
|
1274
1406
|
});
|
|
1275
1407
|
writeSetupSection(input.io, `Building schema context for ${input.connectionId}`, [
|
|
1276
|
-
'Running
|
|
1408
|
+
'Running database scan…',
|
|
1277
1409
|
]);
|
|
1278
1410
|
let scanIo = createBufferedCommandIo();
|
|
1279
1411
|
let scanCode = await scanConnection(input.projectDir, input.connectionId, scanIo);
|
|
@@ -1281,7 +1413,7 @@ async function validateAndScanConnection(input) {
|
|
|
1281
1413
|
const nativeSqliteDetail = nativeSqliteAbiMismatchDetail(`${scanIo.stderrText()}\n${scanIo.stdoutText()}`);
|
|
1282
1414
|
if (nativeSqliteDetail) {
|
|
1283
1415
|
writePrefixedLines((chunk) => input.io.stderr.write(chunk), [
|
|
1284
|
-
`
|
|
1416
|
+
`Database scan failed for ${input.connectionId}.`,
|
|
1285
1417
|
'Native SQLite is built for a different Node.js ABI.',
|
|
1286
1418
|
`Detail: ${nativeSqliteDetail}`,
|
|
1287
1419
|
'Rebuilding Native SQLite with pnpm run native:rebuild…',
|
|
@@ -1289,7 +1421,7 @@ async function validateAndScanConnection(input) {
|
|
|
1289
1421
|
const rebuildNativeSqlite = input.deps.rebuildNativeSqlite ?? defaultRebuildNativeSqlite;
|
|
1290
1422
|
const rebuildCode = await rebuildNativeSqlite(input.io);
|
|
1291
1423
|
if (rebuildCode === 0) {
|
|
1292
|
-
writePrefixedLines((chunk) => input.io.stderr.write(chunk), 'Native SQLite rebuild complete. Retrying
|
|
1424
|
+
writePrefixedLines((chunk) => input.io.stderr.write(chunk), 'Native SQLite rebuild complete. Retrying database scan…');
|
|
1293
1425
|
const retryScanIo = createBufferedCommandIo();
|
|
1294
1426
|
scanCode = await scanConnection(input.projectDir, input.connectionId, retryScanIo);
|
|
1295
1427
|
scanIo = retryScanIo;
|
|
@@ -1297,30 +1429,42 @@ async function validateAndScanConnection(input) {
|
|
|
1297
1429
|
if (scanCode !== 0) {
|
|
1298
1430
|
writePrefixedLines((chunk) => input.io.stderr.write(chunk), [
|
|
1299
1431
|
rebuildCode === 0
|
|
1300
|
-
? `
|
|
1432
|
+
? `Database scan still failed for ${input.connectionId} after rebuilding Native SQLite.`
|
|
1301
1433
|
: `Native SQLite rebuild failed for ${input.connectionId}.`,
|
|
1302
1434
|
'Fix: pnpm run native:rebuild',
|
|
1303
|
-
`Retry: ktx ingest ${input.connectionId} --project-dir ${input.projectDir}
|
|
1435
|
+
`Retry: ktx ingest ${input.connectionId} --project-dir ${input.projectDir}`,
|
|
1304
1436
|
].join('\n'));
|
|
1305
1437
|
}
|
|
1306
1438
|
}
|
|
1307
1439
|
else {
|
|
1308
1440
|
flushPrefixedBufferedCommandOutput(input.io, scanIo);
|
|
1309
1441
|
writePrefixedLines((chunk) => input.io.stderr.write(chunk), [
|
|
1310
|
-
`
|
|
1311
|
-
`Debug command: ktx ingest ${input.connectionId} --project-dir ${input.projectDir} --
|
|
1442
|
+
`Database scan failed for ${input.connectionId}.`,
|
|
1443
|
+
`Debug command: ktx ingest ${input.connectionId} --project-dir ${input.projectDir} --debug`,
|
|
1312
1444
|
].join('\n'));
|
|
1313
1445
|
}
|
|
1314
1446
|
if (scanCode !== 0) {
|
|
1315
|
-
return queryHistoryAvailable
|
|
1447
|
+
return queryHistoryAvailable
|
|
1448
|
+
? failedValidateResult()
|
|
1449
|
+
: queryHistoryUnavailableResult(input.projectDir, input.connectionId);
|
|
1316
1450
|
}
|
|
1317
1451
|
}
|
|
1318
1452
|
const scanOutput = scanIo.stdoutText();
|
|
1319
1453
|
writeSetupSection(input.io, `Schema context complete for ${input.connectionId}`, [`Changes: ${summarizeScanChanges(scanOutput)}`]);
|
|
1454
|
+
if (queryHistoryAvailable) {
|
|
1455
|
+
await maybeProposeQueryHistoryFilters({
|
|
1456
|
+
projectDir: input.projectDir,
|
|
1457
|
+
connectionId: input.connectionId,
|
|
1458
|
+
io: input.io,
|
|
1459
|
+
deps: input.deps,
|
|
1460
|
+
args: input.args,
|
|
1461
|
+
prompts: input.prompts,
|
|
1462
|
+
});
|
|
1463
|
+
}
|
|
1320
1464
|
writeSetupSection(input.io, 'Database ready', [
|
|
1321
1465
|
`${input.connectionId} · ${driverDisplay} · schema context complete`,
|
|
1322
1466
|
]);
|
|
1323
|
-
return
|
|
1467
|
+
return okValidateResult();
|
|
1324
1468
|
}
|
|
1325
1469
|
async function chooseDrivers(args, io, prompts, options) {
|
|
1326
1470
|
if (args.databaseDrivers && args.databaseDrivers.length > 0) {
|
|
@@ -1334,7 +1478,7 @@ async function chooseDrivers(args, io, prompts, options) {
|
|
|
1334
1478
|
return 'missing-input';
|
|
1335
1479
|
}
|
|
1336
1480
|
const initialValues = unique(options?.initialDrivers ?? []);
|
|
1337
|
-
createKtxSetupUiAdapter().note(`Get demo credentials
|
|
1481
|
+
createKtxSetupUiAdapter().note(`Get demo credentials: ${KTX_DEMO_START_URL}`, '🎁 Need a warehouse to play with?', io);
|
|
1338
1482
|
const choices = await prompts.multiselect({
|
|
1339
1483
|
message: withMultiselectNavigation('Which databases should KTX connect to?'),
|
|
1340
1484
|
options: [...DRIVER_OPTIONS],
|
|
@@ -1424,64 +1568,137 @@ async function choosePrimarySourceToEdit(input) {
|
|
|
1424
1568
|
});
|
|
1425
1569
|
return choice === 'back' ? 'back' : choice;
|
|
1426
1570
|
}
|
|
1427
|
-
async function
|
|
1571
|
+
async function configureDatabaseConnection(input) {
|
|
1428
1572
|
const project = await loadKtxProject({ projectDir: input.projectDir });
|
|
1429
|
-
const
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
writePrefixedLines((chunk) => input.io.stderr.write(chunk), `Connection "${input.connectionId}" is not a configured database.`);
|
|
1433
|
-
return 'failed';
|
|
1434
|
-
}
|
|
1435
|
-
const rollback = await createConnectionConfigRollback(input.projectDir, input.connectionId);
|
|
1436
|
-
const replacement = await buildConnectionConfig({
|
|
1437
|
-
driver,
|
|
1573
|
+
const latestConnection = project.config.connections[input.connectionId];
|
|
1574
|
+
let connection = await buildConnectionConfig({
|
|
1575
|
+
driver: input.driver,
|
|
1438
1576
|
connectionId: input.connectionId,
|
|
1439
1577
|
args: input.args,
|
|
1440
1578
|
prompts: input.prompts,
|
|
1441
|
-
existingConnection:
|
|
1579
|
+
existingConnection: latestConnection,
|
|
1442
1580
|
});
|
|
1443
|
-
|
|
1444
|
-
await
|
|
1581
|
+
while (!connection && input.args.inputMode !== 'disabled') {
|
|
1582
|
+
const action = await input.prompts.select(missingConnectionDetailsPrompt(driverLabel(input.driver), input.canReturnToDriverSelection));
|
|
1583
|
+
if (action === 'back') {
|
|
1584
|
+
return 'back';
|
|
1585
|
+
}
|
|
1586
|
+
connection = await buildConnectionConfig({
|
|
1587
|
+
driver: input.driver,
|
|
1588
|
+
connectionId: input.connectionId,
|
|
1589
|
+
args: input.args,
|
|
1590
|
+
prompts: input.prompts,
|
|
1591
|
+
existingConnection: latestConnection,
|
|
1592
|
+
});
|
|
1593
|
+
}
|
|
1594
|
+
if (connection === 'back') {
|
|
1445
1595
|
return 'back';
|
|
1446
1596
|
}
|
|
1447
|
-
if (!
|
|
1448
|
-
|
|
1449
|
-
return '
|
|
1597
|
+
if (!connection) {
|
|
1598
|
+
input.io.stderr.write(`Missing connection details for ${driverLabel(input.driver)}.\n`);
|
|
1599
|
+
return 'cancelled';
|
|
1450
1600
|
}
|
|
1451
1601
|
const withHistoricSql = await maybeApplyHistoricSqlConfig({
|
|
1452
|
-
connection
|
|
1453
|
-
driver,
|
|
1602
|
+
connection,
|
|
1603
|
+
driver: input.driver,
|
|
1454
1604
|
args: input.args,
|
|
1455
1605
|
prompts: input.prompts,
|
|
1456
1606
|
});
|
|
1457
1607
|
if (withHistoricSql === 'back') {
|
|
1458
|
-
await rollback();
|
|
1459
1608
|
return 'back';
|
|
1460
1609
|
}
|
|
1461
1610
|
await writeConnectionConfig({
|
|
1462
1611
|
projectDir: input.projectDir,
|
|
1463
1612
|
connectionId: input.connectionId,
|
|
1464
|
-
connection:
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1613
|
+
connection: input.editBaseline
|
|
1614
|
+
? withExistingPrimaryEditPromptDefaults({
|
|
1615
|
+
previous: input.editBaseline,
|
|
1616
|
+
next: withHistoricSql,
|
|
1617
|
+
driver: input.driver,
|
|
1618
|
+
})
|
|
1619
|
+
: withHistoricSql,
|
|
1620
|
+
io: input.io,
|
|
1621
|
+
});
|
|
1622
|
+
return 'configured';
|
|
1623
|
+
}
|
|
1624
|
+
async function runDatabaseConnectionSetupWithRecovery(input) {
|
|
1625
|
+
let configureCalls = 0;
|
|
1626
|
+
// `configureDatabaseConnection` returns 'cancelled' only when required
|
|
1627
|
+
// connection details are absent in non-interactive mode. The recovery
|
|
1628
|
+
// primitive collapses that into 'failed', so we track it here to restore the
|
|
1629
|
+
// distinct 'missing-input' outcome the surrounding step reports for
|
|
1630
|
+
// incomplete flags (vs. a real connection/probe failure).
|
|
1631
|
+
let sawMissingInput = false;
|
|
1632
|
+
const outcome = await runConnectionSetupWithRecovery({
|
|
1633
|
+
label: input.connectionId,
|
|
1634
|
+
interactive: input.interactive ?? input.args.inputMode !== 'disabled',
|
|
1635
|
+
allowSkip: input.allowSkip,
|
|
1469
1636
|
io: input.io,
|
|
1637
|
+
prompts: input.prompts,
|
|
1638
|
+
snapshot: () => createConnectionConfigRollback(input.projectDir, input.connectionId),
|
|
1639
|
+
configure: async () => {
|
|
1640
|
+
configureCalls += 1;
|
|
1641
|
+
if (input.reuseExistingOnFirstConfigure && configureCalls === 1) {
|
|
1642
|
+
const historicSqlResult = await applyHistoricSqlConfigToExistingConnection({
|
|
1643
|
+
projectDir: input.projectDir,
|
|
1644
|
+
connectionId: input.connectionId,
|
|
1645
|
+
args: input.args,
|
|
1646
|
+
prompts: input.prompts,
|
|
1647
|
+
});
|
|
1648
|
+
return historicSqlResult === 'back' ? 'back' : 'configured';
|
|
1649
|
+
}
|
|
1650
|
+
const configured = await configureDatabaseConnection({
|
|
1651
|
+
projectDir: input.projectDir,
|
|
1652
|
+
connectionId: input.connectionId,
|
|
1653
|
+
driver: input.driver,
|
|
1654
|
+
args: input.args,
|
|
1655
|
+
prompts: input.prompts,
|
|
1656
|
+
io: input.io,
|
|
1657
|
+
canReturnToDriverSelection: input.canReturnToDriverSelection,
|
|
1658
|
+
editBaseline: input.editBaseline,
|
|
1659
|
+
});
|
|
1660
|
+
if (configured === 'cancelled') {
|
|
1661
|
+
sawMissingInput = true;
|
|
1662
|
+
}
|
|
1663
|
+
return configured;
|
|
1664
|
+
},
|
|
1665
|
+
validate: () => validateAndScanConnection({
|
|
1666
|
+
projectDir: input.projectDir,
|
|
1667
|
+
connectionId: input.connectionId,
|
|
1668
|
+
io: input.io,
|
|
1669
|
+
deps: input.deps,
|
|
1670
|
+
args: input.args,
|
|
1671
|
+
prompts: input.prompts,
|
|
1672
|
+
forceScopeAndTables: input.forceScopeAndTables,
|
|
1673
|
+
}),
|
|
1470
1674
|
});
|
|
1471
|
-
|
|
1675
|
+
if (outcome === 'failed' && sawMissingInput) {
|
|
1676
|
+
return 'missing-input';
|
|
1677
|
+
}
|
|
1678
|
+
return outcome;
|
|
1679
|
+
}
|
|
1680
|
+
async function runPrimarySourceFullEdit(input) {
|
|
1681
|
+
const project = await loadKtxProject({ projectDir: input.projectDir });
|
|
1682
|
+
const existing = project.config.connections[input.connectionId];
|
|
1683
|
+
const driver = normalizeDriver(existing?.driver);
|
|
1684
|
+
if (!existing || !driver) {
|
|
1685
|
+
writePrefixedLines((chunk) => input.io.stderr.write(chunk), `Connection "${input.connectionId}" is not a configured database.`);
|
|
1686
|
+
return 'failed';
|
|
1687
|
+
}
|
|
1688
|
+
const outcome = await runDatabaseConnectionSetupWithRecovery({
|
|
1472
1689
|
projectDir: input.projectDir,
|
|
1473
1690
|
connectionId: input.connectionId,
|
|
1474
|
-
|
|
1475
|
-
deps: input.deps,
|
|
1691
|
+
driver,
|
|
1476
1692
|
args: input.args,
|
|
1477
1693
|
prompts: input.prompts,
|
|
1694
|
+
io: input.io,
|
|
1695
|
+
deps: input.deps,
|
|
1696
|
+
canReturnToDriverSelection: true,
|
|
1697
|
+
allowSkip: false,
|
|
1478
1698
|
forceScopeAndTables: true,
|
|
1699
|
+
editBaseline: existing,
|
|
1479
1700
|
});
|
|
1480
|
-
|
|
1481
|
-
await rollback();
|
|
1482
|
-
return validated === 'failed-query-history-unavailable' ? 'failed' : validated;
|
|
1483
|
-
}
|
|
1484
|
-
return 'ready';
|
|
1701
|
+
return outcome === 'skip' ? 'back' : outcome;
|
|
1485
1702
|
}
|
|
1486
1703
|
export async function runKtxSetupDatabasesStep(args, io, deps = {}) {
|
|
1487
1704
|
if (args.skipDatabases) {
|
|
@@ -1492,29 +1709,37 @@ export async function runKtxSetupDatabasesStep(args, io, deps = {}) {
|
|
|
1492
1709
|
if (args.databaseConnectionIds && args.databaseConnectionIds.length > 0) {
|
|
1493
1710
|
const selectedConnectionIds = [];
|
|
1494
1711
|
for (const connectionId of unique(args.databaseConnectionIds)) {
|
|
1495
|
-
const
|
|
1712
|
+
const project = await loadKtxProject({ projectDir: args.projectDir });
|
|
1713
|
+
const driver = normalizeDriver(project.config.connections[connectionId]?.driver);
|
|
1714
|
+
if (!driver) {
|
|
1715
|
+
writePrefixedLines((chunk) => io.stderr.write(chunk), `Connection "${connectionId}" is not configured.`);
|
|
1716
|
+
return { status: 'failed', projectDir: args.projectDir };
|
|
1717
|
+
}
|
|
1718
|
+
const setupOutcome = await runDatabaseConnectionSetupWithRecovery({
|
|
1496
1719
|
projectDir: args.projectDir,
|
|
1497
1720
|
connectionId,
|
|
1721
|
+
driver,
|
|
1498
1722
|
args,
|
|
1499
1723
|
prompts,
|
|
1500
|
-
});
|
|
1501
|
-
if (historicSqlResult === 'back')
|
|
1502
|
-
return { status: 'back', projectDir: args.projectDir };
|
|
1503
|
-
const setupStatus = await validateAndScanConnection({
|
|
1504
|
-
projectDir: args.projectDir,
|
|
1505
|
-
connectionId,
|
|
1506
1724
|
io,
|
|
1507
1725
|
deps,
|
|
1508
|
-
|
|
1509
|
-
|
|
1726
|
+
canReturnToDriverSelection: false,
|
|
1727
|
+
allowSkip: false,
|
|
1728
|
+
interactive: false,
|
|
1729
|
+
reuseExistingOnFirstConfigure: true,
|
|
1510
1730
|
});
|
|
1511
|
-
if (
|
|
1731
|
+
if (setupOutcome === 'back') {
|
|
1512
1732
|
return { status: 'back', projectDir: args.projectDir };
|
|
1513
1733
|
}
|
|
1514
|
-
if (
|
|
1734
|
+
if (setupOutcome === 'missing-input') {
|
|
1735
|
+
return { status: 'missing-input', projectDir: args.projectDir };
|
|
1736
|
+
}
|
|
1737
|
+
if (setupOutcome === 'failed') {
|
|
1515
1738
|
return { status: 'failed', projectDir: args.projectDir };
|
|
1516
1739
|
}
|
|
1517
|
-
|
|
1740
|
+
if (setupOutcome === 'ready') {
|
|
1741
|
+
selectedConnectionIds.push(connectionId);
|
|
1742
|
+
}
|
|
1518
1743
|
}
|
|
1519
1744
|
await markDatabasesComplete(args.projectDir, selectedConnectionIds);
|
|
1520
1745
|
return { status: 'ready', projectDir: args.projectDir, connectionIds: selectedConnectionIds };
|
|
@@ -1563,6 +1788,9 @@ export async function runKtxSetupDatabasesStep(args, io, deps = {}) {
|
|
|
1563
1788
|
showConfiguredPrimaryMenu = true;
|
|
1564
1789
|
continue;
|
|
1565
1790
|
}
|
|
1791
|
+
if (editResult === 'missing-input') {
|
|
1792
|
+
return { status: 'missing-input', projectDir: args.projectDir };
|
|
1793
|
+
}
|
|
1566
1794
|
if (editResult === 'failed') {
|
|
1567
1795
|
return { status: 'failed', projectDir: args.projectDir };
|
|
1568
1796
|
}
|
|
@@ -1617,7 +1845,6 @@ export async function runKtxSetupDatabasesStep(args, io, deps = {}) {
|
|
|
1617
1845
|
io.stderr.write('Missing database connection id: pass --database-connection-id.\n');
|
|
1618
1846
|
return { status: 'missing-input', projectDir: args.projectDir };
|
|
1619
1847
|
}
|
|
1620
|
-
let connectionAlreadyValidated = false;
|
|
1621
1848
|
if (connectionChoice.kind === 'edit') {
|
|
1622
1849
|
const editResult = await runPrimarySourceFullEdit({
|
|
1623
1850
|
projectDir: args.projectDir,
|
|
@@ -1633,231 +1860,44 @@ export async function runKtxSetupDatabasesStep(args, io, deps = {}) {
|
|
|
1633
1860
|
returnToDriverSelection = true;
|
|
1634
1861
|
break;
|
|
1635
1862
|
}
|
|
1636
|
-
if (editResult === '
|
|
1637
|
-
return { status: 'failed', projectDir: args.projectDir };
|
|
1638
|
-
}
|
|
1639
|
-
connectionAlreadyValidated = true;
|
|
1640
|
-
}
|
|
1641
|
-
else if (connectionChoice.kind === 'new') {
|
|
1642
|
-
let connection = await buildConnectionConfig({
|
|
1643
|
-
driver,
|
|
1644
|
-
connectionId: connectionChoice.connectionId,
|
|
1645
|
-
args,
|
|
1646
|
-
prompts,
|
|
1647
|
-
});
|
|
1648
|
-
if (connection === 'back') {
|
|
1649
|
-
if (!canReturnToDriverSelection)
|
|
1650
|
-
return { status: 'back', projectDir: args.projectDir };
|
|
1651
|
-
returnToDriverSelection = true;
|
|
1652
|
-
break;
|
|
1653
|
-
}
|
|
1654
|
-
while (!connection && args.inputMode !== 'disabled') {
|
|
1655
|
-
const label = driverLabel(driver);
|
|
1656
|
-
const action = await prompts.select(missingConnectionDetailsPrompt(label, canReturnToDriverSelection));
|
|
1657
|
-
if (action === 'back') {
|
|
1658
|
-
if (!canReturnToDriverSelection)
|
|
1659
|
-
return { status: 'back', projectDir: args.projectDir };
|
|
1660
|
-
returnToDriverSelection = true;
|
|
1661
|
-
break;
|
|
1662
|
-
}
|
|
1663
|
-
connection = await buildConnectionConfig({
|
|
1664
|
-
driver,
|
|
1665
|
-
connectionId: connectionChoice.connectionId,
|
|
1666
|
-
args,
|
|
1667
|
-
prompts,
|
|
1668
|
-
});
|
|
1669
|
-
if (connection === 'back') {
|
|
1670
|
-
if (!canReturnToDriverSelection)
|
|
1671
|
-
return { status: 'back', projectDir: args.projectDir };
|
|
1672
|
-
returnToDriverSelection = true;
|
|
1673
|
-
break;
|
|
1674
|
-
}
|
|
1675
|
-
}
|
|
1676
|
-
if (returnToDriverSelection) {
|
|
1677
|
-
break;
|
|
1678
|
-
}
|
|
1679
|
-
if (connection === 'back') {
|
|
1680
|
-
break;
|
|
1681
|
-
}
|
|
1682
|
-
if (!connection) {
|
|
1683
|
-
io.stderr.write(`Missing connection details for ${driverLabel(driver)}.\n`);
|
|
1863
|
+
if (editResult === 'missing-input') {
|
|
1684
1864
|
return { status: 'missing-input', projectDir: args.projectDir };
|
|
1685
1865
|
}
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
if (!canReturnToDriverSelection)
|
|
1689
|
-
return { status: 'back', projectDir: args.projectDir };
|
|
1690
|
-
returnToDriverSelection = true;
|
|
1691
|
-
break;
|
|
1692
|
-
}
|
|
1693
|
-
const withContextDepth = await maybeApplyContextDepthConfig({
|
|
1694
|
-
projectDir: args.projectDir,
|
|
1695
|
-
connectionId: connectionChoice.connectionId,
|
|
1696
|
-
connection: withHistoricSql,
|
|
1697
|
-
args,
|
|
1698
|
-
prompts,
|
|
1699
|
-
});
|
|
1700
|
-
if (withContextDepth === 'back') {
|
|
1701
|
-
if (!canReturnToDriverSelection)
|
|
1702
|
-
return { status: 'back', projectDir: args.projectDir };
|
|
1703
|
-
returnToDriverSelection = true;
|
|
1704
|
-
break;
|
|
1866
|
+
if (editResult === 'failed') {
|
|
1867
|
+
return { status: 'failed', projectDir: args.projectDir };
|
|
1705
1868
|
}
|
|
1706
|
-
await writeConnectionConfig({
|
|
1707
|
-
projectDir: args.projectDir,
|
|
1708
|
-
connectionId: connectionChoice.connectionId,
|
|
1709
|
-
connection: withContextDepth,
|
|
1710
|
-
io,
|
|
1711
|
-
});
|
|
1712
1869
|
}
|
|
1713
1870
|
else {
|
|
1714
|
-
const
|
|
1715
|
-
const withHistoricSql = await maybeApplyHistoricSqlConfig({ connection: existing, driver, args, prompts });
|
|
1716
|
-
if (withHistoricSql === 'back') {
|
|
1717
|
-
if (!canReturnToDriverSelection)
|
|
1718
|
-
return { status: 'back', projectDir: args.projectDir };
|
|
1719
|
-
returnToDriverSelection = true;
|
|
1720
|
-
break;
|
|
1721
|
-
}
|
|
1722
|
-
const withContextDepth = await maybeApplyContextDepthConfig({
|
|
1871
|
+
const setupOutcome = await runDatabaseConnectionSetupWithRecovery({
|
|
1723
1872
|
projectDir: args.projectDir,
|
|
1724
1873
|
connectionId: connectionChoice.connectionId,
|
|
1725
|
-
|
|
1874
|
+
driver,
|
|
1726
1875
|
args,
|
|
1727
1876
|
prompts,
|
|
1728
|
-
});
|
|
1729
|
-
if (withContextDepth === 'back') {
|
|
1730
|
-
if (!canReturnToDriverSelection)
|
|
1731
|
-
return { status: 'back', projectDir: args.projectDir };
|
|
1732
|
-
returnToDriverSelection = true;
|
|
1733
|
-
break;
|
|
1734
|
-
}
|
|
1735
|
-
await writeConnectionConfig({
|
|
1736
|
-
projectDir: args.projectDir,
|
|
1737
|
-
connectionId: connectionChoice.connectionId,
|
|
1738
|
-
connection: withContextDepth,
|
|
1739
|
-
io,
|
|
1740
|
-
});
|
|
1741
|
-
}
|
|
1742
|
-
let connectionSkipped = false;
|
|
1743
|
-
let setupStatus = connectionAlreadyValidated
|
|
1744
|
-
? 'ready'
|
|
1745
|
-
: await validateAndScanConnection({
|
|
1746
|
-
projectDir: args.projectDir,
|
|
1747
|
-
connectionId: connectionChoice.connectionId,
|
|
1748
1877
|
io,
|
|
1749
1878
|
deps,
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
while (!connectionAlreadyValidated && setupStatus !== 'ready') {
|
|
1754
|
-
if (setupStatus === 'back') {
|
|
1755
|
-
if (!canReturnToDriverSelection)
|
|
1756
|
-
return { status: 'back', projectDir: args.projectDir };
|
|
1757
|
-
returnToDriverSelection = true;
|
|
1758
|
-
break;
|
|
1759
|
-
}
|
|
1760
|
-
if (args.inputMode === 'disabled')
|
|
1761
|
-
return { status: 'failed', projectDir: args.projectDir };
|
|
1762
|
-
const failureOptions = [
|
|
1763
|
-
{ value: 'retry', label: 'Retry connection test' },
|
|
1764
|
-
{ value: 're-enter', label: 'Re-enter connection details' },
|
|
1765
|
-
...(setupStatus === 'failed-query-history-unavailable'
|
|
1766
|
-
? [{ value: 'disable-query-history', label: 'Disable query history and retry' }]
|
|
1767
|
-
: []),
|
|
1768
|
-
{ value: 'skip', label: 'Skip this database' },
|
|
1769
|
-
{ value: 'back', label: 'Back' },
|
|
1770
|
-
];
|
|
1771
|
-
const action = await prompts.select({
|
|
1772
|
-
message: `Database setup failed for ${connectionChoice.connectionId}`,
|
|
1773
|
-
options: failureOptions,
|
|
1879
|
+
canReturnToDriverSelection,
|
|
1880
|
+
allowSkip: true,
|
|
1881
|
+
reuseExistingOnFirstConfigure: connectionChoice.kind === 'existing',
|
|
1774
1882
|
});
|
|
1775
|
-
if (
|
|
1883
|
+
if (setupOutcome === 'back') {
|
|
1776
1884
|
if (!canReturnToDriverSelection)
|
|
1777
1885
|
return { status: 'back', projectDir: args.projectDir };
|
|
1778
1886
|
returnToDriverSelection = true;
|
|
1779
1887
|
break;
|
|
1780
1888
|
}
|
|
1781
|
-
if (
|
|
1782
|
-
|
|
1783
|
-
break;
|
|
1784
|
-
}
|
|
1785
|
-
if (action === 'retry') {
|
|
1786
|
-
setupStatus = await validateAndScanConnection({
|
|
1787
|
-
projectDir: args.projectDir,
|
|
1788
|
-
connectionId: connectionChoice.connectionId,
|
|
1789
|
-
io,
|
|
1790
|
-
deps,
|
|
1791
|
-
args,
|
|
1792
|
-
prompts,
|
|
1793
|
-
});
|
|
1889
|
+
if (setupOutcome === 'missing-input') {
|
|
1890
|
+
return { status: 'missing-input', projectDir: args.projectDir };
|
|
1794
1891
|
}
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
setupStatus = await validateAndScanConnection({
|
|
1798
|
-
projectDir: args.projectDir,
|
|
1799
|
-
connectionId: connectionChoice.connectionId,
|
|
1800
|
-
io,
|
|
1801
|
-
deps,
|
|
1802
|
-
args,
|
|
1803
|
-
prompts,
|
|
1804
|
-
});
|
|
1892
|
+
if (setupOutcome === 'failed') {
|
|
1893
|
+
return { status: 'failed', projectDir: args.projectDir };
|
|
1805
1894
|
}
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
driver,
|
|
1809
|
-
connectionId: connectionChoice.connectionId,
|
|
1810
|
-
args,
|
|
1811
|
-
prompts,
|
|
1812
|
-
});
|
|
1813
|
-
if (connection === 'back') {
|
|
1814
|
-
if (!canReturnToDriverSelection)
|
|
1815
|
-
return { status: 'back', projectDir: args.projectDir };
|
|
1816
|
-
returnToDriverSelection = true;
|
|
1817
|
-
break;
|
|
1818
|
-
}
|
|
1819
|
-
if (!connection)
|
|
1820
|
-
continue;
|
|
1821
|
-
const withHistoricSql = await maybeApplyHistoricSqlConfig({ connection, driver, args, prompts });
|
|
1822
|
-
if (withHistoricSql === 'back') {
|
|
1823
|
-
if (!canReturnToDriverSelection)
|
|
1824
|
-
return { status: 'back', projectDir: args.projectDir };
|
|
1825
|
-
returnToDriverSelection = true;
|
|
1826
|
-
break;
|
|
1827
|
-
}
|
|
1828
|
-
const withContextDepth = await maybeApplyContextDepthConfig({
|
|
1829
|
-
projectDir: args.projectDir,
|
|
1830
|
-
connectionId: connectionChoice.connectionId,
|
|
1831
|
-
connection: withHistoricSql,
|
|
1832
|
-
args,
|
|
1833
|
-
prompts,
|
|
1834
|
-
});
|
|
1835
|
-
if (withContextDepth === 'back') {
|
|
1836
|
-
if (!canReturnToDriverSelection)
|
|
1837
|
-
return { status: 'back', projectDir: args.projectDir };
|
|
1838
|
-
returnToDriverSelection = true;
|
|
1839
|
-
break;
|
|
1840
|
-
}
|
|
1841
|
-
await writeConnectionConfig({
|
|
1842
|
-
projectDir: args.projectDir,
|
|
1843
|
-
connectionId: connectionChoice.connectionId,
|
|
1844
|
-
connection: withContextDepth,
|
|
1845
|
-
io,
|
|
1846
|
-
});
|
|
1847
|
-
setupStatus = await validateAndScanConnection({
|
|
1848
|
-
projectDir: args.projectDir,
|
|
1849
|
-
connectionId: connectionChoice.connectionId,
|
|
1850
|
-
io,
|
|
1851
|
-
deps,
|
|
1852
|
-
args,
|
|
1853
|
-
prompts,
|
|
1854
|
-
});
|
|
1895
|
+
if (setupOutcome === 'skip') {
|
|
1896
|
+
continue;
|
|
1855
1897
|
}
|
|
1856
1898
|
}
|
|
1857
1899
|
if (returnToDriverSelection)
|
|
1858
1900
|
break;
|
|
1859
|
-
if (connectionSkipped)
|
|
1860
|
-
continue;
|
|
1861
1901
|
pushUniqueConnectionId(selectedConnectionIds, connectionChoice.connectionId);
|
|
1862
1902
|
}
|
|
1863
1903
|
if (returnToDriverSelection) {
|