ardent-cli 0.0.29 → 0.0.30
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/dist/index.js +382 -5
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1042,6 +1042,349 @@ async function multiSelect(items, promptForItem, options = {}) {
|
|
|
1042
1042
|
return accepted;
|
|
1043
1043
|
}
|
|
1044
1044
|
|
|
1045
|
+
// src/lib/replica_identity_prompt.ts
|
|
1046
|
+
import { createInterface as createInterface2 } from "readline/promises";
|
|
1047
|
+
|
|
1048
|
+
// src/lib/replica_identity_decisions.ts
|
|
1049
|
+
import { readFileSync as readFileSync3 } from "fs";
|
|
1050
|
+
var REPLICA_IDENTITY_DECISIONS = [
|
|
1051
|
+
"exclude",
|
|
1052
|
+
"add_pk",
|
|
1053
|
+
"replica_identity_full"
|
|
1054
|
+
];
|
|
1055
|
+
function isReplicaIdentityDecision(value) {
|
|
1056
|
+
return typeof value === "string" && REPLICA_IDENTITY_DECISIONS.includes(value);
|
|
1057
|
+
}
|
|
1058
|
+
function loadDecisionsFromFile(path) {
|
|
1059
|
+
let raw;
|
|
1060
|
+
try {
|
|
1061
|
+
raw = readFileSync3(path, "utf-8");
|
|
1062
|
+
} catch (err) {
|
|
1063
|
+
throw new Error(
|
|
1064
|
+
`Could not read replica-identity decisions file ${path}: ${err instanceof Error ? err.message : String(err)}`
|
|
1065
|
+
);
|
|
1066
|
+
}
|
|
1067
|
+
let parsed;
|
|
1068
|
+
try {
|
|
1069
|
+
parsed = JSON.parse(raw);
|
|
1070
|
+
} catch (err) {
|
|
1071
|
+
throw new Error(
|
|
1072
|
+
`Replica-identity decisions file ${path} is not valid JSON: ${err instanceof Error ? err.message : String(err)}`
|
|
1073
|
+
);
|
|
1074
|
+
}
|
|
1075
|
+
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
1076
|
+
throw new Error(
|
|
1077
|
+
`Replica-identity decisions file ${path} must be a JSON object mapping "database.schema.table" to a decision.`
|
|
1078
|
+
);
|
|
1079
|
+
}
|
|
1080
|
+
const decisions = {};
|
|
1081
|
+
for (const [key, value] of Object.entries(parsed)) {
|
|
1082
|
+
const parts = key.split(".");
|
|
1083
|
+
if (parts.length !== 3 || parts.some((part) => part.length === 0)) {
|
|
1084
|
+
throw new Error(
|
|
1085
|
+
`Replica-identity decisions file ${path}: key ${JSON.stringify(
|
|
1086
|
+
key
|
|
1087
|
+
)} must be of form "database.schema.table" with three non-empty components.`
|
|
1088
|
+
);
|
|
1089
|
+
}
|
|
1090
|
+
if (!isReplicaIdentityDecision(value)) {
|
|
1091
|
+
throw new Error(
|
|
1092
|
+
`Replica-identity decisions file ${path}: value for ${JSON.stringify(
|
|
1093
|
+
key
|
|
1094
|
+
)} must be one of ${REPLICA_IDENTITY_DECISIONS.join(
|
|
1095
|
+
", "
|
|
1096
|
+
)} (got ${JSON.stringify(value)}).`
|
|
1097
|
+
);
|
|
1098
|
+
}
|
|
1099
|
+
decisions[key] = value;
|
|
1100
|
+
}
|
|
1101
|
+
return decisions;
|
|
1102
|
+
}
|
|
1103
|
+
function validateDecisionsAgainstPreflight(decisions, preflight) {
|
|
1104
|
+
const known = new Set(
|
|
1105
|
+
preflight.no_replication_identity_tables.map((row) => row.fqn)
|
|
1106
|
+
);
|
|
1107
|
+
const unknown = Object.keys(decisions).filter((fqn) => !known.has(fqn)).sort();
|
|
1108
|
+
if (unknown.length > 0) {
|
|
1109
|
+
throw new Error(
|
|
1110
|
+
"Replica-identity decisions reference tables that are not in this connector's current preflight list: " + unknown.join(", ") + ".\n Re-run `ardent connector list` to refresh, then re-submit using FQNs from the connector's preflight (key shape: database.schema.table)."
|
|
1111
|
+
);
|
|
1112
|
+
}
|
|
1113
|
+
const missing = preflight.no_replication_identity_tables.map((row) => row.fqn).filter((fqn) => !Object.prototype.hasOwnProperty.call(decisions, fqn)).sort();
|
|
1114
|
+
if (missing.length > 0) {
|
|
1115
|
+
throw new Error(
|
|
1116
|
+
"Replica-identity decisions file is incomplete. It must include a decision for every table in this connector's current preflight list. Missing: " + missing.join(", ") + ".\n Add those FQNs to the file, or re-run with --accept-replica-identity-defaults to explicitly keep existing choices and exclude undecided tables."
|
|
1117
|
+
);
|
|
1118
|
+
}
|
|
1119
|
+
}
|
|
1120
|
+
function buildDefaultedDecisions(preflight) {
|
|
1121
|
+
const decisions = {};
|
|
1122
|
+
for (const row of preflight.no_replication_identity_tables) {
|
|
1123
|
+
decisions[row.fqn] = row.current_decision;
|
|
1124
|
+
}
|
|
1125
|
+
return decisions;
|
|
1126
|
+
}
|
|
1127
|
+
function formatRowCountEstimate(value) {
|
|
1128
|
+
if (value === null || value < 0) return "unknown";
|
|
1129
|
+
return value.toLocaleString("en-US");
|
|
1130
|
+
}
|
|
1131
|
+
|
|
1132
|
+
// src/lib/replica_identity_prompt.ts
|
|
1133
|
+
var DECISION_DESCRIPTIONS = {
|
|
1134
|
+
exclude: "Exclude the table from replication. Branches will not carry rows for this table.",
|
|
1135
|
+
add_pk: "After adding a PRIMARY KEY on source, keep this table in replication. Setup checks the source before replication starts.",
|
|
1136
|
+
replica_identity_full: "You will run ALTER TABLE ... REPLICA IDENTITY FULL on source. Increases WAL volume on UPDATE/DELETE for this table."
|
|
1137
|
+
};
|
|
1138
|
+
function isInteractive() {
|
|
1139
|
+
return Boolean(process.stdin.isTTY && process.stdout.isTTY);
|
|
1140
|
+
}
|
|
1141
|
+
function logPreflightTable(rows) {
|
|
1142
|
+
console.log("");
|
|
1143
|
+
const noun = rows.length === 1 ? "table" : "tables";
|
|
1144
|
+
console.log(
|
|
1145
|
+
`Found ${rows.length} ${noun} on your source database with no replication identity:`
|
|
1146
|
+
);
|
|
1147
|
+
for (const row of rows) {
|
|
1148
|
+
const estimate = formatRowCountEstimate(row.row_count_estimate);
|
|
1149
|
+
console.log(
|
|
1150
|
+
` - ${row.fqn} (~${estimate} rows, currently: ${row.current_decision})`
|
|
1151
|
+
);
|
|
1152
|
+
}
|
|
1153
|
+
console.log("");
|
|
1154
|
+
console.log(
|
|
1155
|
+
"Replication needs a PRIMARY KEY, a UNIQUE NOT NULL column, or REPLICA IDENTITY FULL"
|
|
1156
|
+
);
|
|
1157
|
+
console.log(
|
|
1158
|
+
"to carry UPDATE/DELETE rows. Tables without any of these cannot be replicated."
|
|
1159
|
+
);
|
|
1160
|
+
console.log("Ardent never runs source-side DDL on your behalf.");
|
|
1161
|
+
console.log("");
|
|
1162
|
+
}
|
|
1163
|
+
async function promptForDecision(rl, row, index, total) {
|
|
1164
|
+
const estimate = formatRowCountEstimate(row.row_count_estimate);
|
|
1165
|
+
console.log(
|
|
1166
|
+
`[${index + 1}/${total}] ${row.fqn} (~${estimate} rows, currently: ${row.current_decision})`
|
|
1167
|
+
);
|
|
1168
|
+
for (let optionIndex = 0; optionIndex < REPLICA_IDENTITY_DECISIONS.length; optionIndex += 1) {
|
|
1169
|
+
const option = REPLICA_IDENTITY_DECISIONS[optionIndex];
|
|
1170
|
+
console.log(
|
|
1171
|
+
` ${optionIndex + 1}) ${option} -- ${DECISION_DESCRIPTIONS[option]}`
|
|
1172
|
+
);
|
|
1173
|
+
}
|
|
1174
|
+
const defaultIndex = REPLICA_IDENTITY_DECISIONS.indexOf(row.current_decision);
|
|
1175
|
+
while (true) {
|
|
1176
|
+
const raw = await rl.question(
|
|
1177
|
+
`Choose [1-${REPLICA_IDENTITY_DECISIONS.length}, default=${defaultIndex + 1}]: `
|
|
1178
|
+
);
|
|
1179
|
+
const answer = raw.trim();
|
|
1180
|
+
if (answer === "") return row.current_decision;
|
|
1181
|
+
const numeric = Number.parseInt(answer, 10);
|
|
1182
|
+
if (!Number.isNaN(numeric) && numeric >= 1 && numeric <= REPLICA_IDENTITY_DECISIONS.length) {
|
|
1183
|
+
return REPLICA_IDENTITY_DECISIONS[numeric - 1];
|
|
1184
|
+
}
|
|
1185
|
+
if (REPLICA_IDENTITY_DECISIONS.includes(answer)) {
|
|
1186
|
+
return answer;
|
|
1187
|
+
}
|
|
1188
|
+
console.log(
|
|
1189
|
+
`Unrecognized response. Enter 1-${REPLICA_IDENTITY_DECISIONS.length} or one of: ${REPLICA_IDENTITY_DECISIONS.join(", ")}.`
|
|
1190
|
+
);
|
|
1191
|
+
}
|
|
1192
|
+
}
|
|
1193
|
+
async function runInteractivePrompt(rows) {
|
|
1194
|
+
const rl = createInterface2({ input: process.stdin, output: process.stdout });
|
|
1195
|
+
try {
|
|
1196
|
+
const decisions = {};
|
|
1197
|
+
for (let index = 0; index < rows.length; index += 1) {
|
|
1198
|
+
const row = rows[index];
|
|
1199
|
+
decisions[row.fqn] = await promptForDecision(rl, row, index, rows.length);
|
|
1200
|
+
console.log("");
|
|
1201
|
+
}
|
|
1202
|
+
return decisions;
|
|
1203
|
+
} finally {
|
|
1204
|
+
rl.close();
|
|
1205
|
+
}
|
|
1206
|
+
}
|
|
1207
|
+
function logAcceptDefaults(rows, decisions) {
|
|
1208
|
+
logPreflightTable(rows);
|
|
1209
|
+
console.log(
|
|
1210
|
+
"Saving the current/default decision for every table above. Undecided tables will be excluded from replication."
|
|
1211
|
+
);
|
|
1212
|
+
for (const fqn of Object.keys(decisions).sort()) {
|
|
1213
|
+
console.log(` - ${fqn} -> ${decisions[fqn]}`);
|
|
1214
|
+
}
|
|
1215
|
+
console.log("");
|
|
1216
|
+
}
|
|
1217
|
+
function logSubmissionDisallowed() {
|
|
1218
|
+
console.error("");
|
|
1219
|
+
console.error(
|
|
1220
|
+
"\u2717 Replica-identity decisions cannot be changed while setup is already in progress."
|
|
1221
|
+
);
|
|
1222
|
+
console.error(
|
|
1223
|
+
" Wait for the current setup attempt to finish, then retry setup if the connector still shows setup pending."
|
|
1224
|
+
);
|
|
1225
|
+
console.error(" Contact Ardent support if the connector stays validating.");
|
|
1226
|
+
}
|
|
1227
|
+
function logNonInteractiveRequired(decisionsNeeded) {
|
|
1228
|
+
const noun = decisionsNeeded === 1 ? "table" : "tables";
|
|
1229
|
+
const verb = decisionsNeeded === 1 ? "has" : "have";
|
|
1230
|
+
console.error("");
|
|
1231
|
+
console.error(
|
|
1232
|
+
`\u2717 ${decisionsNeeded} ${noun} on your source database ${verb} no replication identity. A decision is required before setup can continue.`
|
|
1233
|
+
);
|
|
1234
|
+
console.error("");
|
|
1235
|
+
console.error(" This shell is not interactive. Re-run with either:");
|
|
1236
|
+
console.error(
|
|
1237
|
+
' --replica-identity-decisions <path> Complete JSON file: { "database.schema.table": "exclude|add_pk|replica_identity_full" }'
|
|
1238
|
+
);
|
|
1239
|
+
console.error(
|
|
1240
|
+
" --accept-replica-identity-defaults explicitly keep current choices and exclude undecided tables"
|
|
1241
|
+
);
|
|
1242
|
+
}
|
|
1243
|
+
function renderReplicaIdentityFullSql(preflight) {
|
|
1244
|
+
const sql = (preflight?.replica_identity_full_sql ?? "").trim();
|
|
1245
|
+
if (sql.length === 0) return;
|
|
1246
|
+
console.log("");
|
|
1247
|
+
console.log(
|
|
1248
|
+
"Run the following on your source database before the next snapshot:"
|
|
1249
|
+
);
|
|
1250
|
+
console.log("");
|
|
1251
|
+
console.log(sql);
|
|
1252
|
+
console.log("");
|
|
1253
|
+
}
|
|
1254
|
+
async function submitDecisions(connectorId, decisions) {
|
|
1255
|
+
const response = await api.put(
|
|
1256
|
+
`/v1/connectors/${connectorId}/replica-identity-decisions`,
|
|
1257
|
+
{ decisions }
|
|
1258
|
+
);
|
|
1259
|
+
return response.replica_identity_preflight ?? null;
|
|
1260
|
+
}
|
|
1261
|
+
function summarizeDecisions(decisions) {
|
|
1262
|
+
const summary = {
|
|
1263
|
+
decisions_total: Object.keys(decisions).length,
|
|
1264
|
+
decisions_exclude: 0,
|
|
1265
|
+
decisions_add_pk: 0,
|
|
1266
|
+
decisions_replica_identity_full: 0,
|
|
1267
|
+
decisions_unknown: 0
|
|
1268
|
+
};
|
|
1269
|
+
for (const value of Object.values(decisions)) {
|
|
1270
|
+
if (value === "exclude") {
|
|
1271
|
+
summary.decisions_exclude += 1;
|
|
1272
|
+
} else if (value === "add_pk") {
|
|
1273
|
+
summary.decisions_add_pk += 1;
|
|
1274
|
+
} else if (value === "replica_identity_full") {
|
|
1275
|
+
summary.decisions_replica_identity_full += 1;
|
|
1276
|
+
} else {
|
|
1277
|
+
summary.decisions_unknown += 1;
|
|
1278
|
+
}
|
|
1279
|
+
}
|
|
1280
|
+
return summary;
|
|
1281
|
+
}
|
|
1282
|
+
async function handleReplicaIdentityPreflight(connectorId, options, behavior = {}) {
|
|
1283
|
+
const allowDecisionSubmission = behavior.allowDecisionSubmission ?? true;
|
|
1284
|
+
if (options.replicaIdentityDecisions && options.acceptReplicaIdentityDefaults) {
|
|
1285
|
+
console.error(
|
|
1286
|
+
"\u2717 --replica-identity-decisions and --accept-replica-identity-defaults are mutually exclusive."
|
|
1287
|
+
);
|
|
1288
|
+
console.error(" Pass one or the other.");
|
|
1289
|
+
process.exit(1);
|
|
1290
|
+
}
|
|
1291
|
+
let connector;
|
|
1292
|
+
try {
|
|
1293
|
+
connector = await api.get(
|
|
1294
|
+
`/v1/connectors/${connectorId}`
|
|
1295
|
+
);
|
|
1296
|
+
} catch (err) {
|
|
1297
|
+
console.error(
|
|
1298
|
+
"\u2717 Could not fetch connector for replica-identity preflight:",
|
|
1299
|
+
err instanceof Error ? err.message : String(err)
|
|
1300
|
+
);
|
|
1301
|
+
process.exit(1);
|
|
1302
|
+
}
|
|
1303
|
+
const preflight = connector.replica_identity_preflight ?? null;
|
|
1304
|
+
if (!preflight) {
|
|
1305
|
+
return { submitted: false, preflight: null };
|
|
1306
|
+
}
|
|
1307
|
+
if (preflight.status === "unavailable") {
|
|
1308
|
+
trackEvent("CLI: replica identity preflight unavailable", {
|
|
1309
|
+
connector_id: connectorId
|
|
1310
|
+
});
|
|
1311
|
+
console.error("");
|
|
1312
|
+
console.error(
|
|
1313
|
+
"\u2717 Replica identity preflight is unavailable for this connector. Contact Ardent support."
|
|
1314
|
+
);
|
|
1315
|
+
process.exit(1);
|
|
1316
|
+
}
|
|
1317
|
+
if (preflight.decisions_needed === 0) {
|
|
1318
|
+
return { submitted: false, preflight };
|
|
1319
|
+
}
|
|
1320
|
+
const allDecisionsRecorded = preflight.decisions_recorded >= preflight.decisions_needed;
|
|
1321
|
+
if (allDecisionsRecorded && !options.replicaIdentityDecisions && !options.acceptReplicaIdentityDefaults) {
|
|
1322
|
+
renderReplicaIdentityFullSql(preflight);
|
|
1323
|
+
return { submitted: false, preflight };
|
|
1324
|
+
}
|
|
1325
|
+
if (!allowDecisionSubmission) {
|
|
1326
|
+
logSubmissionDisallowed();
|
|
1327
|
+
process.exit(1);
|
|
1328
|
+
}
|
|
1329
|
+
let decisions;
|
|
1330
|
+
let submissionMode;
|
|
1331
|
+
if (options.replicaIdentityDecisions) {
|
|
1332
|
+
try {
|
|
1333
|
+
decisions = loadDecisionsFromFile(options.replicaIdentityDecisions);
|
|
1334
|
+
validateDecisionsAgainstPreflight(decisions, preflight);
|
|
1335
|
+
} catch (err) {
|
|
1336
|
+
console.error(
|
|
1337
|
+
"\u2717",
|
|
1338
|
+
err instanceof Error ? err.message : String(err)
|
|
1339
|
+
);
|
|
1340
|
+
process.exit(1);
|
|
1341
|
+
}
|
|
1342
|
+
submissionMode = "file";
|
|
1343
|
+
} else if (options.acceptReplicaIdentityDefaults) {
|
|
1344
|
+
decisions = buildDefaultedDecisions(preflight);
|
|
1345
|
+
logAcceptDefaults(preflight.no_replication_identity_tables, decisions);
|
|
1346
|
+
submissionMode = "defaults";
|
|
1347
|
+
} else if (isInteractive()) {
|
|
1348
|
+
logPreflightTable(preflight.no_replication_identity_tables);
|
|
1349
|
+
decisions = await runInteractivePrompt(
|
|
1350
|
+
preflight.no_replication_identity_tables
|
|
1351
|
+
);
|
|
1352
|
+
submissionMode = "interactive";
|
|
1353
|
+
} else {
|
|
1354
|
+
trackEvent("CLI: replica identity preflight non_interactive_required", {
|
|
1355
|
+
connector_id: connectorId,
|
|
1356
|
+
decisions_needed: preflight.decisions_needed
|
|
1357
|
+
});
|
|
1358
|
+
logNonInteractiveRequired(preflight.decisions_needed);
|
|
1359
|
+
process.exit(1);
|
|
1360
|
+
}
|
|
1361
|
+
let refreshed;
|
|
1362
|
+
try {
|
|
1363
|
+
refreshed = await submitDecisions(connectorId, decisions);
|
|
1364
|
+
} catch (err) {
|
|
1365
|
+
trackEvent("CLI: replica identity preflight submission failed", {
|
|
1366
|
+
connector_id: connectorId,
|
|
1367
|
+
mode: submissionMode
|
|
1368
|
+
});
|
|
1369
|
+
console.error(
|
|
1370
|
+
"\u2717 Failed to save replica-identity decisions:",
|
|
1371
|
+
err instanceof Error ? err.message : String(err)
|
|
1372
|
+
);
|
|
1373
|
+
process.exit(1);
|
|
1374
|
+
}
|
|
1375
|
+
trackEvent("CLI: replica identity preflight submitted", {
|
|
1376
|
+
connector_id: connectorId,
|
|
1377
|
+
mode: submissionMode,
|
|
1378
|
+
...summarizeDecisions(decisions)
|
|
1379
|
+
});
|
|
1380
|
+
renderReplicaIdentityFullSql(refreshed);
|
|
1381
|
+
const total = Object.keys(decisions).length;
|
|
1382
|
+
console.log(
|
|
1383
|
+
`\u2713 Replica-identity decisions saved (${total} ${total === 1 ? "table" : "tables"}).`
|
|
1384
|
+
);
|
|
1385
|
+
return { submitted: true, preflight: refreshed };
|
|
1386
|
+
}
|
|
1387
|
+
|
|
1045
1388
|
// src/commands/connector/create.ts
|
|
1046
1389
|
function parsePostgresUrl(url) {
|
|
1047
1390
|
const atIndex = url.lastIndexOf("@");
|
|
@@ -1220,7 +1563,15 @@ async function createAction2(type, url, options) {
|
|
|
1220
1563
|
}
|
|
1221
1564
|
const created = await api.post("/v1/connectors", createPayload);
|
|
1222
1565
|
const connectorId = created.id;
|
|
1566
|
+
const replicaIdentityPreflightOptions = {
|
|
1567
|
+
replicaIdentityDecisions: options.replicaIdentityDecisions,
|
|
1568
|
+
acceptReplicaIdentityDefaults: options.acceptReplicaIdentityDefaults
|
|
1569
|
+
};
|
|
1223
1570
|
if (isByoc) {
|
|
1571
|
+
await handleReplicaIdentityPreflight(
|
|
1572
|
+
connectorId,
|
|
1573
|
+
replicaIdentityPreflightOptions
|
|
1574
|
+
);
|
|
1224
1575
|
console.log("Setting up branching engine...");
|
|
1225
1576
|
try {
|
|
1226
1577
|
await runEngineSetupWithPolling(connectorId, connectorName);
|
|
@@ -1282,6 +1633,10 @@ async function createAction2(type, url, options) {
|
|
|
1282
1633
|
}
|
|
1283
1634
|
}
|
|
1284
1635
|
if (connector.branching_engine_status === "configuration_verified") {
|
|
1636
|
+
await handleReplicaIdentityPreflight(
|
|
1637
|
+
connectorId,
|
|
1638
|
+
replicaIdentityPreflightOptions
|
|
1639
|
+
);
|
|
1285
1640
|
console.log("Setting up branching engine...");
|
|
1286
1641
|
try {
|
|
1287
1642
|
await runEngineSetupWithPolling(connectorId, connectorName);
|
|
@@ -1540,7 +1895,7 @@ function connectorRetrySetupFailureTelemetry(err) {
|
|
|
1540
1895
|
}
|
|
1541
1896
|
return { reason: "api_error" };
|
|
1542
1897
|
}
|
|
1543
|
-
async function retrySetupAction(name) {
|
|
1898
|
+
async function retrySetupAction(name, options = {}) {
|
|
1544
1899
|
const currentProjectId = getConfig("currentProjectId");
|
|
1545
1900
|
if (!currentProjectId) {
|
|
1546
1901
|
console.error("\u2717 No current project set. Switch to a project first:");
|
|
@@ -1574,6 +1929,16 @@ async function retrySetupAction(name) {
|
|
|
1574
1929
|
connector_id: connector.id,
|
|
1575
1930
|
starting_engine_status: connector.branching_engine_status ?? null
|
|
1576
1931
|
});
|
|
1932
|
+
await handleReplicaIdentityPreflight(
|
|
1933
|
+
connector.id,
|
|
1934
|
+
{
|
|
1935
|
+
replicaIdentityDecisions: options.replicaIdentityDecisions,
|
|
1936
|
+
acceptReplicaIdentityDefaults: options.acceptReplicaIdentityDefaults
|
|
1937
|
+
},
|
|
1938
|
+
{
|
|
1939
|
+
allowDecisionSubmission: connector.branching_engine_status !== "validating"
|
|
1940
|
+
}
|
|
1941
|
+
);
|
|
1577
1942
|
console.log(`Setting up branching engine for ${name}...`);
|
|
1578
1943
|
try {
|
|
1579
1944
|
const { dispatched } = await runEngineSetupWithPolling(connector.id, name);
|
|
@@ -1768,11 +2133,23 @@ connectorCommand.command("create <type> [url]").description("Create a new connec
|
|
|
1768
2133
|
).option(
|
|
1769
2134
|
"--aws-cluster-name <name>",
|
|
1770
2135
|
"EKS cluster name in the customer account where pgstream pods run (required with --deployment-model=customer-cloud)"
|
|
2136
|
+
).option(
|
|
2137
|
+
"--replica-identity-decisions <path>",
|
|
2138
|
+
'Path to a complete JSON file mapping every current "database.schema.table" preflight FQN -> "exclude" | "add_pk" | "replica_identity_full".'
|
|
2139
|
+
).option(
|
|
2140
|
+
"--accept-replica-identity-defaults",
|
|
2141
|
+
"Non-interactively save the current/default decision for every table with no replication identity. Undecided tables are excluded from replication."
|
|
1771
2142
|
).action(createAction2);
|
|
1772
2143
|
connectorCommand.command("list").description("List your connectors").action(listAction2);
|
|
1773
2144
|
connectorCommand.command("switch <name>").description("Switch to a different connector").action(switchAction2);
|
|
1774
2145
|
connectorCommand.command("retry-setup <name>").description(
|
|
1775
2146
|
"Retry branching engine setup for a connector that didn't finish (status: configuration_verified or validating)"
|
|
2147
|
+
).option(
|
|
2148
|
+
"--replica-identity-decisions <path>",
|
|
2149
|
+
'Path to a complete JSON file mapping every current "database.schema.table" preflight FQN -> "exclude" | "add_pk" | "replica_identity_full".'
|
|
2150
|
+
).option(
|
|
2151
|
+
"--accept-replica-identity-defaults",
|
|
2152
|
+
"Non-interactively save the current/default decision for every table with no replication identity. Undecided tables are excluded from replication."
|
|
1776
2153
|
).action(retrySetupAction);
|
|
1777
2154
|
connectorCommand.command("update <name>").description("Update a connector's configuration").option(
|
|
1778
2155
|
"--drop-extensions <list>",
|
|
@@ -2264,7 +2641,7 @@ projectCommand.command("delete <name>").description("Delete a project by name").
|
|
|
2264
2641
|
import { Command as Command6 } from "commander";
|
|
2265
2642
|
|
|
2266
2643
|
// src/lib/settings.ts
|
|
2267
|
-
import { readFileSync as
|
|
2644
|
+
import { readFileSync as readFileSync4 } from "fs";
|
|
2268
2645
|
var SETTING_KEYS = ["default_db", "branch_sql"];
|
|
2269
2646
|
function requireConnectorAndOrg() {
|
|
2270
2647
|
const connectorId = getConfig("currentConnectorId");
|
|
@@ -2314,7 +2691,7 @@ function resolveValueFromArg(value) {
|
|
|
2314
2691
|
if (value.startsWith("@")) {
|
|
2315
2692
|
const path = value.slice(1);
|
|
2316
2693
|
try {
|
|
2317
|
-
return
|
|
2694
|
+
return readFileSync4(path, "utf-8");
|
|
2318
2695
|
} catch (err) {
|
|
2319
2696
|
throw new Error(
|
|
2320
2697
|
`Failed to read ${path}: ${err instanceof Error ? err.message : String(err)}`
|
|
@@ -2615,7 +2992,7 @@ var logoutCommand = new Command7("logout").description("Logout from Ardent").act
|
|
|
2615
2992
|
var statusCommand = new Command7("status").description("Show status").action(statusAction);
|
|
2616
2993
|
|
|
2617
2994
|
// src/lib/update-check.ts
|
|
2618
|
-
import { existsSync as existsSync2, readFileSync as
|
|
2995
|
+
import { existsSync as existsSync2, readFileSync as readFileSync5, writeFileSync as writeFileSync2 } from "fs";
|
|
2619
2996
|
import { join as join2 } from "path";
|
|
2620
2997
|
import { homedir as homedir2 } from "os";
|
|
2621
2998
|
var UPDATE_CHECK_FILE = join2(homedir2(), ".ardent", "update-check.json");
|
|
@@ -2624,7 +3001,7 @@ var PACKAGE_NAME = "ardent-cli";
|
|
|
2624
3001
|
function loadCache() {
|
|
2625
3002
|
try {
|
|
2626
3003
|
if (existsSync2(UPDATE_CHECK_FILE)) {
|
|
2627
|
-
return JSON.parse(
|
|
3004
|
+
return JSON.parse(readFileSync5(UPDATE_CHECK_FILE, "utf-8"));
|
|
2628
3005
|
}
|
|
2629
3006
|
} catch {
|
|
2630
3007
|
}
|