ardent-cli 0.0.25 → 0.0.27

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.
Files changed (2) hide show
  1. package/dist/index.js +275 -76
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -27,7 +27,11 @@ function formatUpgradeRequiredMessage(text) {
27
27
  try {
28
28
  const json = JSON.parse(text);
29
29
  if (json.detail) {
30
- return `${formatErrorDetail(json.detail)}
30
+ const detail = formatErrorDetail(json.detail);
31
+ if (detail.includes("npm install -g ardent-cli@latest")) {
32
+ return detail;
33
+ }
34
+ return `${detail}
31
35
 
32
36
  npm install -g ardent-cli@latest`;
33
37
  }
@@ -753,29 +757,125 @@ import { Command as Command2 } from "commander";
753
757
  var SUCCESS_ENGINE_STATUSES = /* @__PURE__ */ new Set(["healthy", "degraded"]);
754
758
  var RETRYABLE_ENGINE_STATUSES = /* @__PURE__ */ new Set(["configuration_verified"]);
755
759
  var FAILED_ENGINE_STATUSES = /* @__PURE__ */ new Set(["configuration_failed", "failed_validation"]);
756
- function assertEngineSetupCompleted(operation) {
760
+ var EngineSetupTerminalStatusError = class extends Error {
761
+ kind;
762
+ engineStatus;
763
+ constructor(kind, engineStatus, message) {
764
+ super(message);
765
+ this.name = "EngineSetupTerminalStatusError";
766
+ this.kind = kind;
767
+ this.engineStatus = engineStatus;
768
+ }
769
+ };
770
+ function assertEngineSetupCompleted(operation, connectorName) {
757
771
  const result = operation.result;
758
772
  if (!result) {
759
- throw new Error("Engine setup completed without a result payload.");
773
+ throw new EngineSetupTerminalStatusError(
774
+ "malformed_result",
775
+ null,
776
+ "Engine setup completed without a result payload."
777
+ );
760
778
  }
761
779
  const engineStatus = result.branching_engine_status;
762
780
  if (typeof engineStatus !== "string" || engineStatus.length === 0) {
763
- throw new Error("Engine setup completed without branching_engine_status in the result.");
781
+ throw new EngineSetupTerminalStatusError(
782
+ "malformed_result",
783
+ null,
784
+ "Engine setup completed without branching_engine_status in the result."
785
+ );
764
786
  }
765
787
  if (SUCCESS_ENGINE_STATUSES.has(engineStatus)) {
766
788
  return;
767
789
  }
768
790
  if (RETRYABLE_ENGINE_STATUSES.has(engineStatus)) {
769
- throw new Error(
770
- `Engine setup needs retry (status: ${engineStatus}). Run \`ardent connector list\` to inspect connector state, then retry engine setup.`
791
+ throw new EngineSetupTerminalStatusError(
792
+ "retryable_engine_status",
793
+ engineStatus,
794
+ `Engine setup needs retry (status: ${engineStatus}). Run \`ardent connector retry-setup ${connectorName}\` to retry, or \`ardent connector list\` to inspect connector state first.`
771
795
  );
772
796
  }
773
797
  if (FAILED_ENGINE_STATUSES.has(engineStatus)) {
774
- throw new Error(
798
+ throw new EngineSetupTerminalStatusError(
799
+ "failed_engine_status",
800
+ engineStatus,
775
801
  `Engine setup failed (status: ${engineStatus}). Run \`ardent connector list\` to inspect connector state.`
776
802
  );
777
803
  }
778
- throw new Error(`Engine setup completed with unexpected status: ${engineStatus}.`);
804
+ throw new EngineSetupTerminalStatusError(
805
+ "unexpected_engine_status",
806
+ engineStatus,
807
+ `Engine setup completed with unexpected status: ${engineStatus}.`
808
+ );
809
+ }
810
+
811
+ // src/lib/engine_setup.ts
812
+ var EngineSetupTimeoutError = class extends Error {
813
+ constructor(message) {
814
+ super(message);
815
+ this.name = "EngineSetupTimeoutError";
816
+ }
817
+ };
818
+ var EngineSetupOperationFailedError = class extends Error {
819
+ operationError;
820
+ constructor(operationError, connectorName) {
821
+ super(
822
+ `Engine setup failed: ${operationError ?? "unknown error"}. Inspect connector state with \`ardent connector list\`, then retry with \`ardent connector retry-setup ${connectorName}\` once the underlying issue is resolved.`
823
+ );
824
+ this.name = "EngineSetupOperationFailedError";
825
+ this.operationError = operationError;
826
+ }
827
+ };
828
+ async function runEngineSetupWithPolling(connectorId, connectorName) {
829
+ let dispatch;
830
+ try {
831
+ dispatch = await api.post(
832
+ `/v1/connectors/${connectorId}/engine-setup`,
833
+ {}
834
+ );
835
+ } catch (err) {
836
+ if (isGatewayTimeoutError(err)) {
837
+ throw new Error(
838
+ "Backend dispatch timed out. The engine-setup endpoint is now async and should respond in under a second; this may indicate a backend incident. Try again, or check `ardent connector list` to see if an operation was created anyway."
839
+ );
840
+ }
841
+ throw err;
842
+ }
843
+ const operationId = dispatch?.operation_id;
844
+ if (!operationId) {
845
+ return { dispatched: false };
846
+ }
847
+ const startedAt = Date.now();
848
+ const maxWaitMs = 20 * 60 * 1e3;
849
+ const pollIntervalMs = 5 * 1e3;
850
+ let lastStage = null;
851
+ while (Date.now() - startedAt < maxWaitMs) {
852
+ await new Promise((resolve) => setTimeout(resolve, pollIntervalMs));
853
+ let op;
854
+ try {
855
+ op = await api.get(`/v1/operations/${operationId}`);
856
+ } catch (pollErr) {
857
+ if (isGatewayTimeoutError(pollErr)) continue;
858
+ throw pollErr;
859
+ }
860
+ if (op.stage && op.stage !== lastStage) {
861
+ const progressLabel = op.progress != null ? ` (${op.progress}%)` : "";
862
+ console.log(` ${op.stage}${progressLabel}`);
863
+ lastStage = op.stage;
864
+ }
865
+ if (op.status === "completed") {
866
+ assertEngineSetupCompleted(
867
+ { status: op.status, result: op.result },
868
+ connectorName
869
+ );
870
+ return { dispatched: true };
871
+ }
872
+ if (op.status === "failed") {
873
+ throw new EngineSetupOperationFailedError(op.error, connectorName);
874
+ }
875
+ }
876
+ throw new EngineSetupTimeoutError(
877
+ `Engine setup did not complete within ${maxWaitMs / 6e4} minutes. Setup may still be running server-side \u2014 do NOT delete the connector. Check status with: ardent connector list (operation ${operationId})`
878
+ );
779
879
  }
780
880
 
781
881
  // src/lib/onboarding.ts
@@ -888,63 +988,6 @@ Example:
888
988
  }
889
989
 
890
990
  // src/commands/connector/create.ts
891
- var EngineSetupTimeoutError = class extends Error {
892
- constructor(message) {
893
- super(message);
894
- this.name = "EngineSetupTimeoutError";
895
- }
896
- };
897
- async function runEngineSetupWithPolling(connectorId) {
898
- let dispatch;
899
- try {
900
- dispatch = await api.post(
901
- `/v1/connectors/${connectorId}/engine-setup`,
902
- {}
903
- );
904
- } catch (err) {
905
- if (isGatewayTimeoutError(err)) {
906
- throw new Error(
907
- "Backend dispatch timed out. The engine-setup endpoint is now async and should respond in under a second; this may indicate a backend incident. Try again, or check `ardent connector list` to see if an operation was created anyway."
908
- );
909
- }
910
- throw err;
911
- }
912
- const operationId = dispatch?.operation_id;
913
- if (!operationId) {
914
- return;
915
- }
916
- const startedAt = Date.now();
917
- const maxWaitMs = 20 * 60 * 1e3;
918
- const pollIntervalMs = 5 * 1e3;
919
- let lastStage = null;
920
- while (Date.now() - startedAt < maxWaitMs) {
921
- await new Promise((resolve) => setTimeout(resolve, pollIntervalMs));
922
- let op;
923
- try {
924
- op = await api.get(`/v1/operations/${operationId}`);
925
- } catch (pollErr) {
926
- if (isGatewayTimeoutError(pollErr)) continue;
927
- throw pollErr;
928
- }
929
- if (op.stage && op.stage !== lastStage) {
930
- const progressLabel = op.progress != null ? ` (${op.progress}%)` : "";
931
- console.log(` ${op.stage}${progressLabel}`);
932
- lastStage = op.stage;
933
- }
934
- if (op.status === "completed") {
935
- assertEngineSetupCompleted({ status: op.status, result: op.result });
936
- return;
937
- }
938
- if (op.status === "failed") {
939
- throw new Error(
940
- `Engine setup failed: ${op.error ?? "unknown error"}. Run \`ardent connector list\` to inspect connector state, or re-trigger setup once the underlying issue is resolved.`
941
- );
942
- }
943
- }
944
- throw new EngineSetupTimeoutError(
945
- `Engine setup did not complete within ${maxWaitMs / 6e4} minutes. Setup may still be running server-side \u2014 do NOT delete the connector. Check status with: ardent connector list (operation ${operationId})`
946
- );
947
- }
948
991
  function parsePostgresUrl(url) {
949
992
  const atIndex = url.lastIndexOf("@");
950
993
  const credentialsPart = atIndex > 0 ? url.substring(0, atIndex) : url;
@@ -959,6 +1002,11 @@ function parsePostgresUrl(url) {
959
1002
  if (!username) throw new Error("Username required in connection URL");
960
1003
  return { host, port, username, password };
961
1004
  }
1005
+ function printEngineSetupRecoveryHint(connectorName) {
1006
+ console.error("\u2717 Engine setup did not complete for this connector.");
1007
+ console.error(" Inspect: ardent connector list");
1008
+ console.error(` Retry: ardent connector retry-setup ${connectorName}`);
1009
+ }
962
1010
  async function createAction2(type, url, options) {
963
1011
  const supportedTypes = ["postgresql"];
964
1012
  if (!supportedTypes.includes(type.toLowerCase())) {
@@ -1069,11 +1117,10 @@ async function createAction2(type, url, options) {
1069
1117
  if (isByoc) {
1070
1118
  console.log("Setting up branching engine...");
1071
1119
  try {
1072
- await runEngineSetupWithPolling(connectorId);
1120
+ await runEngineSetupWithPolling(connectorId, connectorName);
1073
1121
  } catch (setupErr) {
1074
1122
  if (!(setupErr instanceof EngineSetupTimeoutError)) {
1075
- console.error("\u2717 Engine setup failed. To retry, delete and recreate:");
1076
- console.error(` ardent connector delete ${connectorName}`);
1123
+ printEngineSetupRecoveryHint(connectorName);
1077
1124
  }
1078
1125
  throw setupErr;
1079
1126
  }
@@ -1100,7 +1147,14 @@ async function createAction2(type, url, options) {
1100
1147
  const connector = await api.get(`/v1/connectors/${connectorId}`);
1101
1148
  if (connector.branching_engine_status === "configuration_verified") {
1102
1149
  console.log("Setting up branching engine...");
1103
- await runEngineSetupWithPolling(connectorId);
1150
+ try {
1151
+ await runEngineSetupWithPolling(connectorId, connectorName);
1152
+ } catch (setupErr) {
1153
+ if (!(setupErr instanceof EngineSetupTimeoutError)) {
1154
+ printEngineSetupRecoveryHint(connectorName);
1155
+ }
1156
+ throw setupErr;
1157
+ }
1104
1158
  }
1105
1159
  }
1106
1160
  const finalConnector = await api.get(`/v1/connectors/${connectorId}`);
@@ -1108,7 +1162,9 @@ async function createAction2(type, url, options) {
1108
1162
  id: connectorId,
1109
1163
  name: connectorName,
1110
1164
  service_name: "postgresql",
1111
- status: finalConnector.branching_engine_status ?? finalConnector.connection_status ?? "pending"
1165
+ status: finalConnector.branching_engine_status ?? finalConnector.connection_status ?? "pending",
1166
+ branching_engine_status: finalConnector.branching_engine_status ?? null,
1167
+ connection_error: finalConnector.connection_error ?? null
1112
1168
  };
1113
1169
  const cached = getCacheEntry("connectors");
1114
1170
  const cachedConnectors = cached?.data || [];
@@ -1140,6 +1196,34 @@ async function createAction2(type, url, options) {
1140
1196
  }
1141
1197
  }
1142
1198
 
1199
+ // src/lib/connector_render.ts
1200
+ var GREEN = "\x1B[32m";
1201
+ var CYAN = "\x1B[36m";
1202
+ var RED = "\x1B[31m";
1203
+ function pendingHint(state, connectorName) {
1204
+ if (state === "validating") {
1205
+ return `engine setup running \u2014 re-run \`ardent connector list\` to refresh, or \`ardent connector retry-setup ${connectorName}\` if it stays this way`;
1206
+ }
1207
+ return `engine setup not finished \u2014 run: ardent connector retry-setup ${connectorName}`;
1208
+ }
1209
+ var ENGINE_PENDING_STATES = /* @__PURE__ */ new Set(["configuration_verified", "validating"]);
1210
+ function renderConnectorIcon(connector) {
1211
+ const engine = connector.branching_engine_status;
1212
+ const statusAllowsEnginePending = connector.status === "healthy" || ENGINE_PENDING_STATES.has(connector.status);
1213
+ if (engine && ENGINE_PENDING_STATES.has(engine) && statusAllowsEnginePending) {
1214
+ return {
1215
+ kind: "engine_pending",
1216
+ icon: "\u25D0",
1217
+ color: CYAN,
1218
+ hint: pendingHint(engine, connector.name)
1219
+ };
1220
+ }
1221
+ if (connector.status === "healthy") {
1222
+ return { kind: "ready", icon: "\u25CF", color: GREEN };
1223
+ }
1224
+ return { kind: "broken", icon: "\u25CB", color: RED };
1225
+ }
1226
+
1143
1227
  // src/commands/connector/list.ts
1144
1228
  async function listAction2() {
1145
1229
  let connectors = [];
@@ -1194,23 +1278,28 @@ async function listAction2() {
1194
1278
  const green2 = "\x1B[32m";
1195
1279
  const dim2 = "\x1B[2m";
1196
1280
  const yellow = "\x1B[33m";
1197
- const reset2 = "\x1B[0m";
1198
1281
  const red = "\x1B[31m";
1282
+ const reset2 = "\x1B[0m";
1199
1283
  console.log("Connectors:\n");
1284
+ let enginePendingCount = 0;
1200
1285
  for (const connector of connectors) {
1201
1286
  const isCurrent = connector.id === currentConnectorId;
1202
- const icon = connector.status === "healthy" ? "\u25CF" : "\u25CB";
1287
+ const render = renderConnectorIcon(connector);
1288
+ if (render.kind === "engine_pending") enginePendingCount += 1;
1203
1289
  const warnings = connector.warnings ?? [];
1204
1290
  const warningSuffix = warnings.length > 0 ? ` ${yellow}\u26A0 ${warnings.length}${reset2}` : "";
1205
- const statusSuffix = connector.status === "healthy" ? "" : ` ${red}[${connector.status}]${reset2}`;
1291
+ const statusSuffix = render.kind === "ready" ? "" : ` ${render.color}[${connector.status}]${reset2}`;
1292
+ const nameLine = isCurrent ? `${green2}* ${render.color}${render.icon}${green2} ${connector.name}${reset2}${warningSuffix}${statusSuffix}` : ` ${render.color}${render.icon}${reset2} ${connector.name}${warningSuffix}${statusSuffix}`;
1293
+ console.log(nameLine);
1206
1294
  if (isCurrent) {
1207
- console.log(`${green2}* ${icon} ${connector.name}${reset2}${warningSuffix}${statusSuffix}`);
1208
1295
  console.log(`${green2} ${connector.service_name}${reset2}`);
1209
1296
  } else {
1210
- console.log(` ${icon} ${connector.name}${warningSuffix}${statusSuffix}`);
1211
1297
  console.log(`${dim2} ${connector.service_name}${reset2}`);
1212
1298
  }
1213
- if (connector.status !== "healthy" && connector.connection_error) {
1299
+ if (render.kind === "engine_pending") {
1300
+ console.log(`${render.color} ${render.hint}${reset2}`);
1301
+ }
1302
+ if (render.kind === "broken" && connector.connection_error) {
1214
1303
  for (const line of connector.connection_error.split("\n")) {
1215
1304
  if (line.trim().length === 0) continue;
1216
1305
  console.log(`${red} ${line}${reset2}`);
@@ -1221,6 +1310,12 @@ async function listAction2() {
1221
1310
  }
1222
1311
  console.log();
1223
1312
  }
1313
+ if (enginePendingCount > 0) {
1314
+ trackEvent("CLI: connector list rendered engine-pending", {
1315
+ engine_pending_count: enginePendingCount,
1316
+ connector_count: connectors.length
1317
+ });
1318
+ }
1224
1319
  }
1225
1320
 
1226
1321
  // src/commands/connector/delete.ts
@@ -1278,6 +1373,106 @@ async function deleteAction2(name) {
1278
1373
  }
1279
1374
  }
1280
1375
 
1376
+ // src/commands/connector/retry-setup.ts
1377
+ function connectorRetrySetupFailureTelemetry(err) {
1378
+ if (isPermissionError(err)) {
1379
+ return { reason: "permission_denied" };
1380
+ }
1381
+ if (err instanceof EngineSetupTimeoutError) {
1382
+ return { reason: "polling_timeout" };
1383
+ }
1384
+ if (err instanceof EngineSetupOperationFailedError) {
1385
+ return {
1386
+ reason: "operation_failed",
1387
+ operation_error: err.operationError ?? "unknown error"
1388
+ };
1389
+ }
1390
+ if (err instanceof EngineSetupTerminalStatusError) {
1391
+ return {
1392
+ reason: err.kind,
1393
+ engine_status: err.engineStatus
1394
+ };
1395
+ }
1396
+ return { reason: "api_error" };
1397
+ }
1398
+ async function retrySetupAction(name) {
1399
+ const currentProjectId = getConfig("currentProjectId");
1400
+ if (!currentProjectId) {
1401
+ console.error("\u2717 No current project set. Switch to a project first:");
1402
+ console.error(" ardent project list");
1403
+ console.error(" ardent project switch <name>");
1404
+ process.exit(1);
1405
+ }
1406
+ let connector;
1407
+ try {
1408
+ const result = await api.get(
1409
+ `/v1/cli/connectors?project_id=${currentProjectId}`
1410
+ );
1411
+ if (!result.connectors) {
1412
+ throw new Error("API returned invalid response: missing connectors array");
1413
+ }
1414
+ setCacheEntry("connectors", result.connectors);
1415
+ connector = result.connectors.find((candidate) => candidate.name === name);
1416
+ } catch (err) {
1417
+ if (isNetworkError(err)) {
1418
+ console.error("\u2717 Cannot run connector retry-setup while offline");
1419
+ process.exit(1);
1420
+ }
1421
+ throw err;
1422
+ }
1423
+ if (!connector) {
1424
+ console.error(`\u2717 Connector "${name}" not found`);
1425
+ console.log(" Run: ardent connector list");
1426
+ process.exit(1);
1427
+ }
1428
+ trackEvent("CLI: connector retry-setup initiated", {
1429
+ connector_id: connector.id,
1430
+ starting_engine_status: connector.branching_engine_status ?? null
1431
+ });
1432
+ console.log(`Setting up branching engine for ${name}...`);
1433
+ try {
1434
+ const { dispatched } = await runEngineSetupWithPolling(connector.id, name);
1435
+ if (dispatched) {
1436
+ trackEvent("CLI: connector retry-setup dispatched", {
1437
+ connector_id: connector.id
1438
+ });
1439
+ }
1440
+ trackEvent("CLI: connector retry-setup succeeded", {
1441
+ connector_id: connector.id,
1442
+ dispatched
1443
+ });
1444
+ try {
1445
+ const refreshed = await api.get(
1446
+ `/v1/cli/connectors?project_id=${currentProjectId}`
1447
+ );
1448
+ if (refreshed.connectors) {
1449
+ setCacheEntry("connectors", refreshed.connectors);
1450
+ }
1451
+ } catch {
1452
+ }
1453
+ if (!dispatched) {
1454
+ console.log("\u2713 Engine already set up \u2014 no work needed");
1455
+ } else {
1456
+ console.log("\u2713 Engine setup complete");
1457
+ }
1458
+ } catch (err) {
1459
+ const failureTelemetry = connectorRetrySetupFailureTelemetry(err);
1460
+ if (isPermissionError(err)) {
1461
+ trackEvent("CLI: connector retry-setup failed", failureTelemetry);
1462
+ console.error("\u2717 You don't have permission to run engine setup on this connector.");
1463
+ process.exit(1);
1464
+ }
1465
+ if (err instanceof EngineSetupTimeoutError) {
1466
+ trackEvent("CLI: connector retry-setup failed", failureTelemetry);
1467
+ console.error(`\u2717 ${err.message}`);
1468
+ process.exit(1);
1469
+ }
1470
+ trackEvent("CLI: connector retry-setup failed", failureTelemetry);
1471
+ console.error("\u2717 Failed:", err instanceof Error ? err.message : err);
1472
+ process.exit(1);
1473
+ }
1474
+ }
1475
+
1281
1476
  // src/commands/connector/switch.ts
1282
1477
  async function switchAction2(name) {
1283
1478
  const cached = getCacheEntry("connectors");
@@ -1351,6 +1546,9 @@ connectorCommand.command("create <type> [url]").description("Create a new connec
1351
1546
  ).action(createAction2);
1352
1547
  connectorCommand.command("list").description("List your connectors").action(listAction2);
1353
1548
  connectorCommand.command("switch <name>").description("Switch to a different connector").action(switchAction2);
1549
+ connectorCommand.command("retry-setup <name>").description(
1550
+ "Retry branching engine setup for a connector that didn't finish (status: configuration_verified or validating)"
1551
+ ).action(retrySetupAction);
1354
1552
  connectorCommand.command("delete <name>").description("Delete a connector by name").action(deleteAction2);
1355
1553
 
1356
1554
  // src/commands/invite/index.ts
@@ -2279,6 +2477,7 @@ CONNECTORS
2279
2477
  connector create Connect a database (postgresql, snowflake, etc.)
2280
2478
  connector list List your connectors (* = current)
2281
2479
  connector switch Switch to a different connector
2480
+ connector retry-setup Retry branching engine setup for a connector
2282
2481
  connector delete Delete a connector
2283
2482
 
2284
2483
  BRANCHES
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ardent-cli",
3
- "version": "0.0.25",
3
+ "version": "0.0.27",
4
4
  "description": "Git for Data infrastructure",
5
5
  "type": "module",
6
6
  "bin": {