ardent-cli 0.0.54 → 0.0.56

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 +109 -6
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -568,6 +568,9 @@ function operationStageDisplay(stage) {
568
568
  const label = STAGE_DISPLAY[base] ?? humanizeRawStage(base);
569
569
  return suffix ? `${label} (for ${suffix})` : label;
570
570
  }
571
+ function operationStageLabel(op) {
572
+ return op.stage_label ?? operationStageDisplay(op.stage);
573
+ }
571
574
 
572
575
  // src/lib/resource_name_validation.ts
573
576
  var RESERVED_SUFFIXES = ["pooler", "readonly", "direct"];
@@ -978,7 +981,7 @@ async function pollBranchCreate(operationId, idempotencyScopeKey, mode) {
978
981
  consecutiveTransientFailures = 0;
979
982
  if (op.stage && op.stage !== lastStage && mode !== "json" && mode !== "print-url") {
980
983
  const progressLabel = op.progress != null ? ` (${op.progress}%)` : "";
981
- console.log(` ${operationStageDisplay(op.stage)}${progressLabel}`);
984
+ console.log(` ${operationStageLabel(op)}${progressLabel}`);
982
985
  lastStage = op.stage;
983
986
  }
984
987
  if (op.status === "completed") {
@@ -1348,7 +1351,7 @@ async function runDiscoveryWithPolling(connectorId, options) {
1348
1351
  }
1349
1352
  if (op.stage && op.stage !== lastStage) {
1350
1353
  const progressLabel = op.progress != null ? ` (${op.progress}%)` : "";
1351
- console.log(` ${operationStageDisplay(op.stage)}${progressLabel}`);
1354
+ console.log(` ${operationStageLabel(op)}${progressLabel}`);
1352
1355
  lastStage = op.stage;
1353
1356
  }
1354
1357
  if (op.status === "completed") {
@@ -1539,7 +1542,7 @@ async function runEngineSetupWithPolling(connectorId, connectorName, options = {
1539
1542
  consecutiveTransientFailures = 0;
1540
1543
  if (op.stage && op.stage !== lastStage) {
1541
1544
  const progressLabel = op.progress != null ? ` (${op.progress}%)` : "";
1542
- console.log(` ${operationStageDisplay(op.stage)}${progressLabel}`);
1545
+ console.log(` ${operationStageLabel(op)}${progressLabel}`);
1543
1546
  lastStage = op.stage;
1544
1547
  }
1545
1548
  if (op.status === "completed") {
@@ -2655,8 +2658,9 @@ async function listAction2() {
2655
2658
  if (render.kind === "engine_pending") enginePendingCount += 1;
2656
2659
  const warnings = connector.warnings ?? [];
2657
2660
  const warningSuffix = warnings.length > 0 ? ` ${yellow}\u26A0 ${warnings.length}${reset2}` : "";
2661
+ const lockSuffix = connector.deletion_locked ? ` ${yellow}[delete locked]${reset2}` : "";
2658
2662
  const statusSuffix = render.kind === "ready" ? "" : ` ${render.color}[${connectorStatusDisplay(connector.status)}]${reset2}`;
2659
- const nameLine = isCurrent ? `${green2}* ${render.color}${render.icon}${green2} ${connector.name}${reset2}${warningSuffix}${statusSuffix}` : ` ${render.color}${render.icon}${reset2} ${connector.name}${warningSuffix}${statusSuffix}`;
2663
+ const nameLine = isCurrent ? `${green2}* ${render.color}${render.icon}${green2} ${connector.name}${reset2}${warningSuffix}${lockSuffix}${statusSuffix}` : ` ${render.color}${render.icon}${reset2} ${connector.name}${warningSuffix}${lockSuffix}${statusSuffix}`;
2660
2664
  console.log(nameLine);
2661
2665
  if (isCurrent) {
2662
2666
  console.log(`${green2} ${connector.service_name}${reset2}`);
@@ -2690,6 +2694,10 @@ async function listAction2() {
2690
2694
  var CONNECTOR_DELETE_MAX_WAIT_MS = 60 * 60 * 1e3;
2691
2695
  var CONNECTOR_DELETE_POLL_INTERVAL_MS = 5 * 1e3;
2692
2696
  var CONNECTOR_DELETE_TRANSIENT_WARN_EVERY = 6;
2697
+ var CONNECTOR_DELETION_LOCKED_MESSAGE = "Connector deletion is locked. Unlock this connector, then retry.";
2698
+ function stripApiErrorPrefix(message) {
2699
+ return message.replace(/^API error \d{3}:\s*/, "");
2700
+ }
2693
2701
  async function deleteAction2(name, options = {}) {
2694
2702
  const cached = getCacheEntry("connectors");
2695
2703
  let connector = cached?.data.find((c) => c.name === name);
@@ -2745,6 +2753,11 @@ async function deleteAction2(name, options = {}) {
2745
2753
  process.exit(1);
2746
2754
  }
2747
2755
  const message = err instanceof Error ? err.message : String(err);
2756
+ if (message.includes(CONNECTOR_DELETION_LOCKED_MESSAGE)) {
2757
+ trackEvent("CLI: connector delete failed", { reason: "deletion_locked" });
2758
+ console.error(`\u2717 ${stripApiErrorPrefix(message)}`);
2759
+ process.exit(1);
2760
+ }
2748
2761
  const isDrainRefusal = message.includes("Branch still has un-replicated changes") || typeof err === "object" && err !== null && "status" in err && err.status === 409;
2749
2762
  if (isDrainRefusal) {
2750
2763
  trackEvent("CLI: connector delete failed", { reason: "drain_refused" });
@@ -2780,7 +2793,7 @@ async function waitForConnectorDelete(operationId) {
2780
2793
  consecutiveTransientFailures = 0;
2781
2794
  if (operation.stage && operation.stage !== lastStage) {
2782
2795
  const progressLabel = operation.progress != null ? ` (${operation.progress}%)` : "";
2783
- console.log(` ${operationStageDisplay(operation.stage)}${progressLabel}`);
2796
+ console.log(` ${operationStageLabel(operation)}${progressLabel}`);
2784
2797
  lastStage = operation.stage;
2785
2798
  }
2786
2799
  if (operation.status === "completed") {
@@ -3250,6 +3263,94 @@ async function updateAction(name, options = {}) {
3250
3263
  }
3251
3264
  }
3252
3265
 
3266
+ // src/commands/connector/lock.ts
3267
+ async function resolveConnectorByName2(name) {
3268
+ const cached = getCacheEntry("connectors");
3269
+ let connector = cached?.data.find((candidate) => candidate.name === name);
3270
+ if (!connector) {
3271
+ try {
3272
+ const result = await api.get("/v1/cli/connectors");
3273
+ if (result.connectors) {
3274
+ setCacheEntry("connectors", result.connectors);
3275
+ connector = result.connectors.find((candidate) => candidate.name === name);
3276
+ }
3277
+ } catch (err) {
3278
+ if (isNetworkError(err)) {
3279
+ console.error("\u2717 Connector not found in cache and offline");
3280
+ process.exit(1);
3281
+ }
3282
+ throw err;
3283
+ }
3284
+ }
3285
+ if (!connector) {
3286
+ console.error(`\u2717 Connector "${name}" not found`);
3287
+ console.log(" Run: ardent connector list");
3288
+ process.exit(1);
3289
+ }
3290
+ return connector;
3291
+ }
3292
+ function refreshCachedConnector(updatedConnector) {
3293
+ const currentCache = getCacheEntry("connectors");
3294
+ if (!currentCache?.data) {
3295
+ return;
3296
+ }
3297
+ const updatedConnectors = currentCache.data.map(
3298
+ (connector) => connector.id === updatedConnector.id ? { ...connector, ...updatedConnector } : connector
3299
+ );
3300
+ setCacheEntry("connectors", updatedConnectors);
3301
+ }
3302
+ async function lockAction(name, options = {}) {
3303
+ const connector = await resolveConnectorByName2(name);
3304
+ try {
3305
+ const response = await api.post(
3306
+ `/v1/connectors/${connector.id}/deletion-lock`,
3307
+ { reason: options.reason ?? null }
3308
+ );
3309
+ refreshCachedConnector({ ...response.connector, deletion_locked: true });
3310
+ trackEvent("CLI: connector deletion lock succeeded");
3311
+ console.log(`\u2713 Connector deletion locked for '${name}'`);
3312
+ } catch (err) {
3313
+ if (isPermissionError(err)) {
3314
+ trackEvent("CLI: connector deletion lock failed", { reason: "permission_denied" });
3315
+ console.error("\u2717 You don't have permission to update this connector.");
3316
+ process.exit(1);
3317
+ }
3318
+ if (isNetworkError(err)) {
3319
+ trackEvent("CLI: connector deletion lock failed", { reason: "offline" });
3320
+ console.error("\u2717 Cannot lock connector deletion while offline");
3321
+ process.exit(1);
3322
+ }
3323
+ trackEvent("CLI: connector deletion lock failed", { reason: "api_error" });
3324
+ console.error("\u2717 Failed:", err instanceof Error ? err.message : err);
3325
+ process.exit(1);
3326
+ }
3327
+ }
3328
+ async function unlockAction(name) {
3329
+ const connector = await resolveConnectorByName2(name);
3330
+ try {
3331
+ const response = await api.delete(
3332
+ `/v1/connectors/${connector.id}/deletion-lock`
3333
+ );
3334
+ refreshCachedConnector({ ...response.connector, deletion_locked: false });
3335
+ trackEvent("CLI: connector deletion unlock succeeded");
3336
+ console.log(`\u2713 Connector deletion unlocked for '${name}'`);
3337
+ } catch (err) {
3338
+ if (isPermissionError(err)) {
3339
+ trackEvent("CLI: connector deletion unlock failed", { reason: "permission_denied" });
3340
+ console.error("\u2717 You don't have permission to update this connector.");
3341
+ process.exit(1);
3342
+ }
3343
+ if (isNetworkError(err)) {
3344
+ trackEvent("CLI: connector deletion unlock failed", { reason: "offline" });
3345
+ console.error("\u2717 Cannot unlock connector deletion while offline");
3346
+ process.exit(1);
3347
+ }
3348
+ trackEvent("CLI: connector deletion unlock failed", { reason: "api_error" });
3349
+ console.error("\u2717 Failed:", err instanceof Error ? err.message : err);
3350
+ process.exit(1);
3351
+ }
3352
+ }
3353
+
3253
3354
  // src/commands/connector/index.ts
3254
3355
  var connectorCommand = new Command2("connector").description("Manage database connectors");
3255
3356
  connectorCommand.command("preflight <type> [url]").description(
@@ -3300,8 +3401,10 @@ connectorCommand.command("update <name>").description("Update a connector's conf
3300
3401
  ).action(updateAction);
3301
3402
  connectorCommand.command("delete <name>").description("Delete a connector by name").option(
3302
3403
  "--force",
3303
- "Skip the in-flight WAL/Kafka drain wait. Any un-replicated changes are abandoned and recorded for operator triage."
3404
+ "Skip the wait for in-flight changes to finish replicating. Any un-replicated changes are abandoned."
3304
3405
  ).action(deleteAction2);
3406
+ connectorCommand.command("lock <name>").description("Lock connector deletion").option("--reason <text>", "Optional reason for the deletion lock").action(lockAction);
3407
+ connectorCommand.command("unlock <name>").description("Unlock connector deletion").action(unlockAction);
3305
3408
 
3306
3409
  // src/commands/invite/index.ts
3307
3410
  import { Command as Command3 } from "commander";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ardent-cli",
3
- "version": "0.0.54",
3
+ "version": "0.0.56",
4
4
  "description": "Git for Data infrastructure",
5
5
  "type": "module",
6
6
  "bin": {