@kaelio/ktx 0.8.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.8.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-runtime.js +50 -3
- package/dist/commands/setup-commands.js +1 -1
- 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/isolated-diff/patch-integrator.js +75 -5
- package/dist/context/ingest/local-adapters.js +21 -4
- package/dist/context/ingest/local-bundle-runtime.js +3 -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/project/config.d.ts +2 -0
- package/dist/context/project/config.js +2 -2
- 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-build-view.js +4 -32
- package/dist/io/buffered-command-io.d.ts +11 -0
- package/dist/io/buffered-command-io.js +28 -0
- 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.d.ts +20 -1
- package/dist/public-ingest.js +178 -27
- package/dist/scan.js +3 -1
- package/dist/setup-context.d.ts +2 -0
- package/dist/setup-context.js +133 -27
- package/dist/setup-databases.d.ts +17 -1
- package/dist/setup-databases.js +358 -249
- 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 +108 -28
- package/dist/setup.js +22 -10
- 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/events.d.ts +11 -6
- package/dist/telemetry/events.js +10 -2
- package/dist/telemetry/identity.d.ts +0 -1
- package/dist/telemetry/identity.js +6 -6
- package/dist/telemetry/index.d.ts +12 -0
- package/dist/telemetry/index.js +13 -2
- package/dist/telemetry/scrubber.d.ts +10 -0
- package/dist/telemetry/scrubber.js +20 -0
- package/package.json +5 -4
package/dist/setup-databases.js
CHANGED
|
@@ -4,15 +4,23 @@ 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
25
|
import { writeProjectLocalSecretReference } from './setup-secrets.js';
|
|
18
26
|
import { isDemoConnection } from './telemetry/demo-detect.js';
|
|
@@ -671,10 +679,13 @@ async function maybeApplyHistoricSqlConfig(input) {
|
|
|
671
679
|
if (!enabled) {
|
|
672
680
|
return withQueryHistoryConfig(input.connection, { ...existing, enabled: false });
|
|
673
681
|
}
|
|
682
|
+
const existingFilters = existing.filters && typeof existing.filters === 'object' && !Array.isArray(existing.filters)
|
|
683
|
+
? existing.filters
|
|
684
|
+
: {};
|
|
674
685
|
const common = {
|
|
675
686
|
...existing,
|
|
676
687
|
enabled: true,
|
|
677
|
-
filters: historicSqlFiltersForSetup(input.args.queryHistoryServiceAccountPatterns),
|
|
688
|
+
filters: historicSqlFiltersForSetup(input.args.queryHistoryServiceAccountPatterns, existingFilters),
|
|
678
689
|
};
|
|
679
690
|
if (dialect === 'postgres') {
|
|
680
691
|
return withQueryHistoryConfig(input.connection, {
|
|
@@ -688,9 +699,10 @@ async function maybeApplyHistoricSqlConfig(input) {
|
|
|
688
699
|
redactionPatterns: input.args.queryHistoryRedactionPatterns ?? [],
|
|
689
700
|
});
|
|
690
701
|
}
|
|
691
|
-
function historicSqlFiltersForSetup(patterns) {
|
|
702
|
+
function historicSqlFiltersForSetup(patterns, existingFilters = {}) {
|
|
692
703
|
const serviceAccountPatterns = patterns ?? [];
|
|
693
704
|
return {
|
|
705
|
+
...existingFilters,
|
|
694
706
|
dropTrivialProbes: true,
|
|
695
707
|
...(serviceAccountPatterns.length > 0
|
|
696
708
|
? {
|
|
@@ -715,29 +727,6 @@ async function defaultScanConnection(projectDir, connectionId, io) {
|
|
|
715
727
|
dryRun: false,
|
|
716
728
|
}, io);
|
|
717
729
|
}
|
|
718
|
-
function createBufferedCommandIo() {
|
|
719
|
-
let stdout = '';
|
|
720
|
-
let stderr = '';
|
|
721
|
-
return {
|
|
722
|
-
stdout: {
|
|
723
|
-
isTTY: false,
|
|
724
|
-
write(chunk) {
|
|
725
|
-
stdout += chunk;
|
|
726
|
-
},
|
|
727
|
-
},
|
|
728
|
-
stderr: {
|
|
729
|
-
write(chunk) {
|
|
730
|
-
stderr += chunk;
|
|
731
|
-
},
|
|
732
|
-
},
|
|
733
|
-
stdoutText() {
|
|
734
|
-
return stdout;
|
|
735
|
-
},
|
|
736
|
-
stderrText() {
|
|
737
|
-
return stderr;
|
|
738
|
-
},
|
|
739
|
-
};
|
|
740
|
-
}
|
|
741
730
|
function envWithCurrentNodeFirst(env = process.env) {
|
|
742
731
|
return {
|
|
743
732
|
...env,
|
|
@@ -889,6 +878,27 @@ async function disableConnectionQueryHistory(projectDir, connectionId) {
|
|
|
889
878
|
connection: withQueryHistoryConfig(connection, { ...existing, enabled: false }),
|
|
890
879
|
});
|
|
891
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
|
+
}
|
|
892
902
|
async function createConnectionConfigRollback(projectDir, connectionId) {
|
|
893
903
|
const project = await loadKtxProject({ projectDir });
|
|
894
904
|
const previousConnection = project.config.connections[connectionId];
|
|
@@ -983,14 +993,14 @@ async function maybeConfigureDatabaseScope(input) {
|
|
|
983
993
|
const connection = project.config.connections[input.connectionId];
|
|
984
994
|
const driver = normalizeDriver(connection?.driver);
|
|
985
995
|
if (!driver || driver === 'sqlite')
|
|
986
|
-
return
|
|
996
|
+
return okValidateResult();
|
|
987
997
|
const spec = SCOPE_DISCOVERY_SPECS[driver];
|
|
988
998
|
const existingTables = connection?.enabled_tables;
|
|
989
999
|
const hasExistingTables = Array.isArray(existingTables) && existingTables.length > 0;
|
|
990
1000
|
const existingScope = spec ? configuredScopeValues(connection, spec) : [];
|
|
991
1001
|
const hasExistingScope = !spec || existingScope.length > 0;
|
|
992
1002
|
if (hasExistingTables && hasExistingScope && input.forcePrompt !== true) {
|
|
993
|
-
return
|
|
1003
|
+
return okValidateResult();
|
|
994
1004
|
}
|
|
995
1005
|
const cliSchemas = input.args.databaseSchemas;
|
|
996
1006
|
if (input.args.inputMode === 'disabled') {
|
|
@@ -1003,7 +1013,7 @@ async function maybeConfigureDatabaseScope(input) {
|
|
|
1003
1013
|
catch (error) {
|
|
1004
1014
|
const detail = error instanceof Error ? error.message : String(error);
|
|
1005
1015
|
input.io.stderr.write(`Could not discover ${spec.promptLabel.toLowerCase()} for ${input.connectionId}; ${detail}\n`);
|
|
1006
|
-
return
|
|
1016
|
+
return okValidateResult();
|
|
1007
1017
|
}
|
|
1008
1018
|
}
|
|
1009
1019
|
if (scopeToWrite.length > 0) {
|
|
@@ -1019,7 +1029,7 @@ async function maybeConfigureDatabaseScope(input) {
|
|
|
1019
1029
|
]);
|
|
1020
1030
|
}
|
|
1021
1031
|
}
|
|
1022
|
-
return
|
|
1032
|
+
return okValidateResult();
|
|
1023
1033
|
}
|
|
1024
1034
|
if (spec && cliSchemas.length > 0) {
|
|
1025
1035
|
await writeScopeConfig({
|
|
@@ -1050,7 +1060,7 @@ async function maybeConfigureDatabaseScope(input) {
|
|
|
1050
1060
|
spec,
|
|
1051
1061
|
});
|
|
1052
1062
|
if (typed === undefined)
|
|
1053
|
-
return
|
|
1063
|
+
return backValidateResult();
|
|
1054
1064
|
effectiveCliSchemas = typed;
|
|
1055
1065
|
listedSchemas = typed;
|
|
1056
1066
|
if (typed.length > 0) {
|
|
@@ -1065,7 +1075,7 @@ async function maybeConfigureDatabaseScope(input) {
|
|
|
1065
1075
|
}
|
|
1066
1076
|
const schemas = unique(listedSchemas);
|
|
1067
1077
|
if (spec && schemas.length === 0) {
|
|
1068
|
-
return
|
|
1078
|
+
return okValidateResult();
|
|
1069
1079
|
}
|
|
1070
1080
|
const schemaSuggestion = effectiveCliSchemas.length > 0
|
|
1071
1081
|
? { excluded: new Set(), suggested: new Set(effectiveCliSchemas) }
|
|
@@ -1093,10 +1103,10 @@ async function maybeConfigureDatabaseScope(input) {
|
|
|
1093
1103
|
writePrefixedLines((chunk) => input.io.stderr.write(chunk), input.forcePrompt === true
|
|
1094
1104
|
? `Could not discover tables for ${input.connectionId}; edit was not saved. ${detail}`
|
|
1095
1105
|
: `Could not discover tables for ${input.connectionId}; continuing without table filter. ${detail}`);
|
|
1096
|
-
return input.forcePrompt === true ?
|
|
1106
|
+
return input.forcePrompt === true ? failedValidateResult() : okValidateResult();
|
|
1097
1107
|
}
|
|
1098
1108
|
if (pickResult.kind === 'back') {
|
|
1099
|
-
return
|
|
1109
|
+
return backValidateResult();
|
|
1100
1110
|
}
|
|
1101
1111
|
const enabledTables = pickResult.enabledTables;
|
|
1102
1112
|
const activeSchemas = pickResult.activeSchemas;
|
|
@@ -1111,7 +1121,7 @@ async function maybeConfigureDatabaseScope(input) {
|
|
|
1111
1121
|
const refreshedProject = await loadKtxProject({ projectDir: input.projectDir });
|
|
1112
1122
|
const currentConnection = refreshedProject.config.connections[input.connectionId];
|
|
1113
1123
|
if (!currentConnection)
|
|
1114
|
-
return
|
|
1124
|
+
return okValidateResult();
|
|
1115
1125
|
await writeConnectionConfig({
|
|
1116
1126
|
projectDir: input.projectDir,
|
|
1117
1127
|
connectionId: input.connectionId,
|
|
@@ -1127,7 +1137,7 @@ async function maybeConfigureDatabaseScope(input) {
|
|
|
1127
1137
|
writeSetupSection(input.io, `Tables enabled for ${input.connectionId}`, [
|
|
1128
1138
|
`✓ ${enabledTables.length} tables enabled`,
|
|
1129
1139
|
]);
|
|
1130
|
-
return
|
|
1140
|
+
return okValidateResult();
|
|
1131
1141
|
}
|
|
1132
1142
|
async function ensureHistoricSqlIngestDefaults(projectDir) {
|
|
1133
1143
|
const project = await loadKtxProject({ projectDir });
|
|
@@ -1188,6 +1198,158 @@ async function maybeRunHistoricSqlSetupProbe(input) {
|
|
|
1188
1198
|
}
|
|
1189
1199
|
return result.ok;
|
|
1190
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
|
+
}
|
|
1191
1353
|
async function applyHistoricSqlConfigToExistingConnection(input) {
|
|
1192
1354
|
if (input.args.inputMode === 'disabled' &&
|
|
1193
1355
|
input.args.enableQueryHistory !== true &&
|
|
@@ -1225,7 +1387,7 @@ async function validateAndScanConnection(input) {
|
|
|
1225
1387
|
if (testCode !== 0) {
|
|
1226
1388
|
flushPrefixedBufferedCommandOutput(input.io, testIo);
|
|
1227
1389
|
writePrefixedLines((chunk) => input.io.stderr.write(chunk), `Connection test failed for ${input.connectionId}.`);
|
|
1228
|
-
return
|
|
1390
|
+
return failedValidateResult();
|
|
1229
1391
|
}
|
|
1230
1392
|
const testOutput = testIo.stdoutText();
|
|
1231
1393
|
const outputDriver = normalizeDriver(readOutputValue(testOutput, 'Driver'));
|
|
@@ -1233,7 +1395,7 @@ async function validateAndScanConnection(input) {
|
|
|
1233
1395
|
const testLines = ['✓ Connection test passed', `Driver: ${driverDisplay}`];
|
|
1234
1396
|
writeSetupSection(input.io, `Testing ${input.connectionId}`, testLines);
|
|
1235
1397
|
const scopeStatus = await maybeConfigureDatabaseScope({ ...input, forcePrompt: input.forceScopeAndTables });
|
|
1236
|
-
if (scopeStatus !== '
|
|
1398
|
+
if (scopeStatus.status !== 'ok') {
|
|
1237
1399
|
return scopeStatus;
|
|
1238
1400
|
}
|
|
1239
1401
|
const queryHistoryAvailable = await maybeRunHistoricSqlSetupProbe({
|
|
@@ -1282,15 +1444,27 @@ async function validateAndScanConnection(input) {
|
|
|
1282
1444
|
].join('\n'));
|
|
1283
1445
|
}
|
|
1284
1446
|
if (scanCode !== 0) {
|
|
1285
|
-
return queryHistoryAvailable
|
|
1447
|
+
return queryHistoryAvailable
|
|
1448
|
+
? failedValidateResult()
|
|
1449
|
+
: queryHistoryUnavailableResult(input.projectDir, input.connectionId);
|
|
1286
1450
|
}
|
|
1287
1451
|
}
|
|
1288
1452
|
const scanOutput = scanIo.stdoutText();
|
|
1289
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
|
+
}
|
|
1290
1464
|
writeSetupSection(input.io, 'Database ready', [
|
|
1291
1465
|
`${input.connectionId} · ${driverDisplay} · schema context complete`,
|
|
1292
1466
|
]);
|
|
1293
|
-
return
|
|
1467
|
+
return okValidateResult();
|
|
1294
1468
|
}
|
|
1295
1469
|
async function chooseDrivers(args, io, prompts, options) {
|
|
1296
1470
|
if (args.databaseDrivers && args.databaseDrivers.length > 0) {
|
|
@@ -1394,64 +1568,137 @@ async function choosePrimarySourceToEdit(input) {
|
|
|
1394
1568
|
});
|
|
1395
1569
|
return choice === 'back' ? 'back' : choice;
|
|
1396
1570
|
}
|
|
1397
|
-
async function
|
|
1571
|
+
async function configureDatabaseConnection(input) {
|
|
1398
1572
|
const project = await loadKtxProject({ projectDir: input.projectDir });
|
|
1399
|
-
const
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
writePrefixedLines((chunk) => input.io.stderr.write(chunk), `Connection "${input.connectionId}" is not a configured database.`);
|
|
1403
|
-
return 'failed';
|
|
1404
|
-
}
|
|
1405
|
-
const rollback = await createConnectionConfigRollback(input.projectDir, input.connectionId);
|
|
1406
|
-
const replacement = await buildConnectionConfig({
|
|
1407
|
-
driver,
|
|
1573
|
+
const latestConnection = project.config.connections[input.connectionId];
|
|
1574
|
+
let connection = await buildConnectionConfig({
|
|
1575
|
+
driver: input.driver,
|
|
1408
1576
|
connectionId: input.connectionId,
|
|
1409
1577
|
args: input.args,
|
|
1410
1578
|
prompts: input.prompts,
|
|
1411
|
-
existingConnection:
|
|
1579
|
+
existingConnection: latestConnection,
|
|
1412
1580
|
});
|
|
1413
|
-
|
|
1414
|
-
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') {
|
|
1415
1595
|
return 'back';
|
|
1416
1596
|
}
|
|
1417
|
-
if (!
|
|
1418
|
-
|
|
1419
|
-
return '
|
|
1597
|
+
if (!connection) {
|
|
1598
|
+
input.io.stderr.write(`Missing connection details for ${driverLabel(input.driver)}.\n`);
|
|
1599
|
+
return 'cancelled';
|
|
1420
1600
|
}
|
|
1421
1601
|
const withHistoricSql = await maybeApplyHistoricSqlConfig({
|
|
1422
|
-
connection
|
|
1423
|
-
driver,
|
|
1602
|
+
connection,
|
|
1603
|
+
driver: input.driver,
|
|
1424
1604
|
args: input.args,
|
|
1425
1605
|
prompts: input.prompts,
|
|
1426
1606
|
});
|
|
1427
1607
|
if (withHistoricSql === 'back') {
|
|
1428
|
-
await rollback();
|
|
1429
1608
|
return 'back';
|
|
1430
1609
|
}
|
|
1431
1610
|
await writeConnectionConfig({
|
|
1432
1611
|
projectDir: input.projectDir,
|
|
1433
1612
|
connectionId: input.connectionId,
|
|
1434
|
-
connection:
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
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,
|
|
1439
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
|
+
}),
|
|
1440
1674
|
});
|
|
1441
|
-
|
|
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({
|
|
1442
1689
|
projectDir: input.projectDir,
|
|
1443
1690
|
connectionId: input.connectionId,
|
|
1444
|
-
|
|
1445
|
-
deps: input.deps,
|
|
1691
|
+
driver,
|
|
1446
1692
|
args: input.args,
|
|
1447
1693
|
prompts: input.prompts,
|
|
1694
|
+
io: input.io,
|
|
1695
|
+
deps: input.deps,
|
|
1696
|
+
canReturnToDriverSelection: true,
|
|
1697
|
+
allowSkip: false,
|
|
1448
1698
|
forceScopeAndTables: true,
|
|
1699
|
+
editBaseline: existing,
|
|
1449
1700
|
});
|
|
1450
|
-
|
|
1451
|
-
await rollback();
|
|
1452
|
-
return validated === 'failed-query-history-unavailable' ? 'failed' : validated;
|
|
1453
|
-
}
|
|
1454
|
-
return 'ready';
|
|
1701
|
+
return outcome === 'skip' ? 'back' : outcome;
|
|
1455
1702
|
}
|
|
1456
1703
|
export async function runKtxSetupDatabasesStep(args, io, deps = {}) {
|
|
1457
1704
|
if (args.skipDatabases) {
|
|
@@ -1462,29 +1709,37 @@ export async function runKtxSetupDatabasesStep(args, io, deps = {}) {
|
|
|
1462
1709
|
if (args.databaseConnectionIds && args.databaseConnectionIds.length > 0) {
|
|
1463
1710
|
const selectedConnectionIds = [];
|
|
1464
1711
|
for (const connectionId of unique(args.databaseConnectionIds)) {
|
|
1465
|
-
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({
|
|
1466
1719
|
projectDir: args.projectDir,
|
|
1467
1720
|
connectionId,
|
|
1721
|
+
driver,
|
|
1468
1722
|
args,
|
|
1469
1723
|
prompts,
|
|
1470
|
-
});
|
|
1471
|
-
if (historicSqlResult === 'back')
|
|
1472
|
-
return { status: 'back', projectDir: args.projectDir };
|
|
1473
|
-
const setupStatus = await validateAndScanConnection({
|
|
1474
|
-
projectDir: args.projectDir,
|
|
1475
|
-
connectionId,
|
|
1476
1724
|
io,
|
|
1477
1725
|
deps,
|
|
1478
|
-
|
|
1479
|
-
|
|
1726
|
+
canReturnToDriverSelection: false,
|
|
1727
|
+
allowSkip: false,
|
|
1728
|
+
interactive: false,
|
|
1729
|
+
reuseExistingOnFirstConfigure: true,
|
|
1480
1730
|
});
|
|
1481
|
-
if (
|
|
1731
|
+
if (setupOutcome === 'back') {
|
|
1482
1732
|
return { status: 'back', projectDir: args.projectDir };
|
|
1483
1733
|
}
|
|
1484
|
-
if (
|
|
1734
|
+
if (setupOutcome === 'missing-input') {
|
|
1735
|
+
return { status: 'missing-input', projectDir: args.projectDir };
|
|
1736
|
+
}
|
|
1737
|
+
if (setupOutcome === 'failed') {
|
|
1485
1738
|
return { status: 'failed', projectDir: args.projectDir };
|
|
1486
1739
|
}
|
|
1487
|
-
|
|
1740
|
+
if (setupOutcome === 'ready') {
|
|
1741
|
+
selectedConnectionIds.push(connectionId);
|
|
1742
|
+
}
|
|
1488
1743
|
}
|
|
1489
1744
|
await markDatabasesComplete(args.projectDir, selectedConnectionIds);
|
|
1490
1745
|
return { status: 'ready', projectDir: args.projectDir, connectionIds: selectedConnectionIds };
|
|
@@ -1533,6 +1788,9 @@ export async function runKtxSetupDatabasesStep(args, io, deps = {}) {
|
|
|
1533
1788
|
showConfiguredPrimaryMenu = true;
|
|
1534
1789
|
continue;
|
|
1535
1790
|
}
|
|
1791
|
+
if (editResult === 'missing-input') {
|
|
1792
|
+
return { status: 'missing-input', projectDir: args.projectDir };
|
|
1793
|
+
}
|
|
1536
1794
|
if (editResult === 'failed') {
|
|
1537
1795
|
return { status: 'failed', projectDir: args.projectDir };
|
|
1538
1796
|
}
|
|
@@ -1587,7 +1845,6 @@ export async function runKtxSetupDatabasesStep(args, io, deps = {}) {
|
|
|
1587
1845
|
io.stderr.write('Missing database connection id: pass --database-connection-id.\n');
|
|
1588
1846
|
return { status: 'missing-input', projectDir: args.projectDir };
|
|
1589
1847
|
}
|
|
1590
|
-
let connectionAlreadyValidated = false;
|
|
1591
1848
|
if (connectionChoice.kind === 'edit') {
|
|
1592
1849
|
const editResult = await runPrimarySourceFullEdit({
|
|
1593
1850
|
projectDir: args.projectDir,
|
|
@@ -1603,192 +1860,44 @@ export async function runKtxSetupDatabasesStep(args, io, deps = {}) {
|
|
|
1603
1860
|
returnToDriverSelection = true;
|
|
1604
1861
|
break;
|
|
1605
1862
|
}
|
|
1606
|
-
if (editResult === '
|
|
1607
|
-
return { status: 'failed', projectDir: args.projectDir };
|
|
1608
|
-
}
|
|
1609
|
-
connectionAlreadyValidated = true;
|
|
1610
|
-
}
|
|
1611
|
-
else if (connectionChoice.kind === 'new') {
|
|
1612
|
-
let connection = await buildConnectionConfig({
|
|
1613
|
-
driver,
|
|
1614
|
-
connectionId: connectionChoice.connectionId,
|
|
1615
|
-
args,
|
|
1616
|
-
prompts,
|
|
1617
|
-
});
|
|
1618
|
-
if (connection === 'back') {
|
|
1619
|
-
if (!canReturnToDriverSelection)
|
|
1620
|
-
return { status: 'back', projectDir: args.projectDir };
|
|
1621
|
-
returnToDriverSelection = true;
|
|
1622
|
-
break;
|
|
1623
|
-
}
|
|
1624
|
-
while (!connection && args.inputMode !== 'disabled') {
|
|
1625
|
-
const label = driverLabel(driver);
|
|
1626
|
-
const action = await prompts.select(missingConnectionDetailsPrompt(label, canReturnToDriverSelection));
|
|
1627
|
-
if (action === 'back') {
|
|
1628
|
-
if (!canReturnToDriverSelection)
|
|
1629
|
-
return { status: 'back', projectDir: args.projectDir };
|
|
1630
|
-
returnToDriverSelection = true;
|
|
1631
|
-
break;
|
|
1632
|
-
}
|
|
1633
|
-
connection = await buildConnectionConfig({
|
|
1634
|
-
driver,
|
|
1635
|
-
connectionId: connectionChoice.connectionId,
|
|
1636
|
-
args,
|
|
1637
|
-
prompts,
|
|
1638
|
-
});
|
|
1639
|
-
if (connection === 'back') {
|
|
1640
|
-
if (!canReturnToDriverSelection)
|
|
1641
|
-
return { status: 'back', projectDir: args.projectDir };
|
|
1642
|
-
returnToDriverSelection = true;
|
|
1643
|
-
break;
|
|
1644
|
-
}
|
|
1645
|
-
}
|
|
1646
|
-
if (returnToDriverSelection) {
|
|
1647
|
-
break;
|
|
1648
|
-
}
|
|
1649
|
-
if (connection === 'back') {
|
|
1650
|
-
break;
|
|
1651
|
-
}
|
|
1652
|
-
if (!connection) {
|
|
1653
|
-
io.stderr.write(`Missing connection details for ${driverLabel(driver)}.\n`);
|
|
1863
|
+
if (editResult === 'missing-input') {
|
|
1654
1864
|
return { status: 'missing-input', projectDir: args.projectDir };
|
|
1655
1865
|
}
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
if (!canReturnToDriverSelection)
|
|
1659
|
-
return { status: 'back', projectDir: args.projectDir };
|
|
1660
|
-
returnToDriverSelection = true;
|
|
1661
|
-
break;
|
|
1866
|
+
if (editResult === 'failed') {
|
|
1867
|
+
return { status: 'failed', projectDir: args.projectDir };
|
|
1662
1868
|
}
|
|
1663
|
-
await writeConnectionConfig({
|
|
1664
|
-
projectDir: args.projectDir,
|
|
1665
|
-
connectionId: connectionChoice.connectionId,
|
|
1666
|
-
connection: withHistoricSql,
|
|
1667
|
-
io,
|
|
1668
|
-
});
|
|
1669
1869
|
}
|
|
1670
1870
|
else {
|
|
1671
|
-
const
|
|
1672
|
-
const withHistoricSql = await maybeApplyHistoricSqlConfig({ connection: existing, driver, args, prompts });
|
|
1673
|
-
if (withHistoricSql === 'back') {
|
|
1674
|
-
if (!canReturnToDriverSelection)
|
|
1675
|
-
return { status: 'back', projectDir: args.projectDir };
|
|
1676
|
-
returnToDriverSelection = true;
|
|
1677
|
-
break;
|
|
1678
|
-
}
|
|
1679
|
-
await writeConnectionConfig({
|
|
1680
|
-
projectDir: args.projectDir,
|
|
1681
|
-
connectionId: connectionChoice.connectionId,
|
|
1682
|
-
connection: withHistoricSql,
|
|
1683
|
-
io,
|
|
1684
|
-
});
|
|
1685
|
-
}
|
|
1686
|
-
let connectionSkipped = false;
|
|
1687
|
-
let setupStatus = connectionAlreadyValidated
|
|
1688
|
-
? 'ready'
|
|
1689
|
-
: await validateAndScanConnection({
|
|
1871
|
+
const setupOutcome = await runDatabaseConnectionSetupWithRecovery({
|
|
1690
1872
|
projectDir: args.projectDir,
|
|
1691
1873
|
connectionId: connectionChoice.connectionId,
|
|
1692
|
-
|
|
1693
|
-
deps,
|
|
1874
|
+
driver,
|
|
1694
1875
|
args,
|
|
1695
1876
|
prompts,
|
|
1877
|
+
io,
|
|
1878
|
+
deps,
|
|
1879
|
+
canReturnToDriverSelection,
|
|
1880
|
+
allowSkip: true,
|
|
1881
|
+
reuseExistingOnFirstConfigure: connectionChoice.kind === 'existing',
|
|
1696
1882
|
});
|
|
1697
|
-
|
|
1698
|
-
if (setupStatus === 'back') {
|
|
1699
|
-
if (!canReturnToDriverSelection)
|
|
1700
|
-
return { status: 'back', projectDir: args.projectDir };
|
|
1701
|
-
returnToDriverSelection = true;
|
|
1702
|
-
break;
|
|
1703
|
-
}
|
|
1704
|
-
if (args.inputMode === 'disabled')
|
|
1705
|
-
return { status: 'failed', projectDir: args.projectDir };
|
|
1706
|
-
const failureOptions = [
|
|
1707
|
-
{ value: 'retry', label: 'Retry connection test' },
|
|
1708
|
-
{ value: 're-enter', label: 'Re-enter connection details' },
|
|
1709
|
-
...(setupStatus === 'failed-query-history-unavailable'
|
|
1710
|
-
? [{ value: 'disable-query-history', label: 'Disable query history and retry' }]
|
|
1711
|
-
: []),
|
|
1712
|
-
{ value: 'skip', label: 'Skip this database' },
|
|
1713
|
-
{ value: 'back', label: 'Back' },
|
|
1714
|
-
];
|
|
1715
|
-
const action = await prompts.select({
|
|
1716
|
-
message: `Database setup failed for ${connectionChoice.connectionId}`,
|
|
1717
|
-
options: failureOptions,
|
|
1718
|
-
});
|
|
1719
|
-
if (action === 'back') {
|
|
1883
|
+
if (setupOutcome === 'back') {
|
|
1720
1884
|
if (!canReturnToDriverSelection)
|
|
1721
1885
|
return { status: 'back', projectDir: args.projectDir };
|
|
1722
1886
|
returnToDriverSelection = true;
|
|
1723
1887
|
break;
|
|
1724
1888
|
}
|
|
1725
|
-
if (
|
|
1726
|
-
|
|
1727
|
-
break;
|
|
1728
|
-
}
|
|
1729
|
-
if (action === 'retry') {
|
|
1730
|
-
setupStatus = await validateAndScanConnection({
|
|
1731
|
-
projectDir: args.projectDir,
|
|
1732
|
-
connectionId: connectionChoice.connectionId,
|
|
1733
|
-
io,
|
|
1734
|
-
deps,
|
|
1735
|
-
args,
|
|
1736
|
-
prompts,
|
|
1737
|
-
});
|
|
1889
|
+
if (setupOutcome === 'missing-input') {
|
|
1890
|
+
return { status: 'missing-input', projectDir: args.projectDir };
|
|
1738
1891
|
}
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
setupStatus = await validateAndScanConnection({
|
|
1742
|
-
projectDir: args.projectDir,
|
|
1743
|
-
connectionId: connectionChoice.connectionId,
|
|
1744
|
-
io,
|
|
1745
|
-
deps,
|
|
1746
|
-
args,
|
|
1747
|
-
prompts,
|
|
1748
|
-
});
|
|
1892
|
+
if (setupOutcome === 'failed') {
|
|
1893
|
+
return { status: 'failed', projectDir: args.projectDir };
|
|
1749
1894
|
}
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
driver,
|
|
1753
|
-
connectionId: connectionChoice.connectionId,
|
|
1754
|
-
args,
|
|
1755
|
-
prompts,
|
|
1756
|
-
});
|
|
1757
|
-
if (connection === 'back') {
|
|
1758
|
-
if (!canReturnToDriverSelection)
|
|
1759
|
-
return { status: 'back', projectDir: args.projectDir };
|
|
1760
|
-
returnToDriverSelection = true;
|
|
1761
|
-
break;
|
|
1762
|
-
}
|
|
1763
|
-
if (!connection)
|
|
1764
|
-
continue;
|
|
1765
|
-
const withHistoricSql = await maybeApplyHistoricSqlConfig({ connection, driver, args, prompts });
|
|
1766
|
-
if (withHistoricSql === 'back') {
|
|
1767
|
-
if (!canReturnToDriverSelection)
|
|
1768
|
-
return { status: 'back', projectDir: args.projectDir };
|
|
1769
|
-
returnToDriverSelection = true;
|
|
1770
|
-
break;
|
|
1771
|
-
}
|
|
1772
|
-
await writeConnectionConfig({
|
|
1773
|
-
projectDir: args.projectDir,
|
|
1774
|
-
connectionId: connectionChoice.connectionId,
|
|
1775
|
-
connection: withHistoricSql,
|
|
1776
|
-
io,
|
|
1777
|
-
});
|
|
1778
|
-
setupStatus = await validateAndScanConnection({
|
|
1779
|
-
projectDir: args.projectDir,
|
|
1780
|
-
connectionId: connectionChoice.connectionId,
|
|
1781
|
-
io,
|
|
1782
|
-
deps,
|
|
1783
|
-
args,
|
|
1784
|
-
prompts,
|
|
1785
|
-
});
|
|
1895
|
+
if (setupOutcome === 'skip') {
|
|
1896
|
+
continue;
|
|
1786
1897
|
}
|
|
1787
1898
|
}
|
|
1788
1899
|
if (returnToDriverSelection)
|
|
1789
1900
|
break;
|
|
1790
|
-
if (connectionSkipped)
|
|
1791
|
-
continue;
|
|
1792
1901
|
pushUniqueConnectionId(selectedConnectionIds, connectionChoice.connectionId);
|
|
1793
1902
|
}
|
|
1794
1903
|
if (returnToDriverSelection) {
|