ardent-cli 0.0.49 → 0.0.51

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 +188 -51
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -6,6 +6,9 @@ import { Command as Command8 } from "commander";
6
6
  // src/commands/branch/index.ts
7
7
  import { Command } from "commander";
8
8
 
9
+ // src/commands/branch/create.ts
10
+ import { randomUUID as randomUUID2 } from "crypto";
11
+
9
12
  // src/lib/config.ts
10
13
  import { existsSync, mkdirSync, readFileSync as readFileSync2, writeFileSync, unlinkSync } from "fs";
11
14
  import { homedir } from "os";
@@ -137,6 +140,24 @@ function getCacheEntry(key) {
137
140
  const config = loadConfig();
138
141
  return config.cache?.[key];
139
142
  }
143
+ function getPendingBranchCreateKey(scopeKey) {
144
+ return getConfig("pendingBranchCreateKeys")?.[scopeKey];
145
+ }
146
+ function setPendingBranchCreateKey(scopeKey, idempotencyKey) {
147
+ const config = loadConfig();
148
+ if (!config.pendingBranchCreateKeys) {
149
+ config.pendingBranchCreateKeys = {};
150
+ }
151
+ config.pendingBranchCreateKeys[scopeKey] = idempotencyKey;
152
+ saveConfig(config);
153
+ }
154
+ function clearPendingBranchCreateKey(scopeKey) {
155
+ const config = loadConfig();
156
+ if (config.pendingBranchCreateKeys && scopeKey in config.pendingBranchCreateKeys) {
157
+ delete config.pendingBranchCreateKeys[scopeKey];
158
+ saveConfig(config);
159
+ }
160
+ }
140
161
  function formatIdle(lastActivityIso) {
141
162
  if (!lastActivityIso) {
142
163
  return "unknown";
@@ -316,11 +337,14 @@ var ApiClient = class {
316
337
  });
317
338
  return this.handleResponse(response);
318
339
  }
319
- async post(path, body) {
340
+ async post(path, body, extraHeaders = {}) {
320
341
  const url = `${getApiUrl()}${path}`;
321
342
  const response = await fetch(url, {
322
343
  method: "POST",
323
- headers: this.getHeaders(),
344
+ headers: {
345
+ ...this.getHeaders(),
346
+ ...extraHeaders
347
+ },
324
348
  body: JSON.stringify(body)
325
349
  });
326
350
  return this.handleResponse(response);
@@ -487,6 +511,58 @@ async function resolveCurrentConnectorId() {
487
511
  );
488
512
  }
489
513
 
514
+ // src/lib/operation_stage_display.ts
515
+ var STAGE_DISPLAY = {
516
+ // engine_setup_worker / postgres_engine_setup
517
+ "dispatched": "Queued",
518
+ "preparing": "Preparing",
519
+ "creating-neon-project": "Provisioning the branch target",
520
+ "preparing-target-databases": "Preparing target databases",
521
+ "deploying-pgstream": "Starting replication",
522
+ "applying-rls": "Applying RLS policies",
523
+ "storing-credentials": "Storing connection credentials",
524
+ "validating": "Validating the branch target",
525
+ // reset_worker / reset_connector
526
+ "resetting": "Resetting",
527
+ "deleting-pgstream": "Stopping replication",
528
+ "rediscovering-source": "Re-checking the source database",
529
+ "resetting-neon-main": "Resetting the branch target",
530
+ "creating-target-schemas": "Recreating target schemas",
531
+ "redeploying-pgstream": "Restarting replication",
532
+ // environment deploy_worker
533
+ "loading_config": "Loading environment configuration",
534
+ "deploying_infrastructure": "Provisioning environment infrastructure",
535
+ "recording_success": "Finalizing environment",
536
+ "cleaning_failed_deploy": "Cleaning up failed environment provisioning",
537
+ // environment destroy_worker
538
+ "deleting_private_links": "Removing private network links",
539
+ "destroying_infrastructure": "Tearing down environment infrastructure",
540
+ "recording_destroy_success": "Finalizing environment teardown",
541
+ // discovery_worker / run_discovery_crawl (ARD-1098)
542
+ "connecting": "Connecting to source",
543
+ "enumerating_databases": "Listing databases",
544
+ "scanning_database": "Scanning schema",
545
+ "writing_schema": "Saving discovered schema",
546
+ "merging_results": "Combining results",
547
+ // branch.create.v1 workflow (ARD-1244)
548
+ "provisioning": "Provisioning the branch",
549
+ "configuring": "Storing connection credentials",
550
+ "recording": "Recording the branch",
551
+ "finalizing": "Finalizing the branch",
552
+ "activating": "Activating the branch"
553
+ };
554
+ function humanizeRawStage(_raw) {
555
+ return "Working";
556
+ }
557
+ function operationStageDisplay(stage) {
558
+ if (!stage) return "Working";
559
+ const colonIndex = stage.indexOf(":");
560
+ const base = colonIndex === -1 ? stage : stage.slice(0, colonIndex);
561
+ const suffix = colonIndex === -1 ? "" : stage.slice(colonIndex + 1);
562
+ const label = STAGE_DISPLAY[base] ?? humanizeRawStage(base);
563
+ return suffix ? `${label} (for ${suffix})` : label;
564
+ }
565
+
490
566
  // src/lib/resource_name_validation.ts
491
567
  var RESERVED_SUFFIXES = ["pooler", "readonly", "direct"];
492
568
  var MAX_RESOURCE_NAME_LENGTH = 100;
@@ -659,6 +735,28 @@ function isMachineReadableBranchInvocation(args2) {
659
735
  }
660
736
 
661
737
  // src/commands/branch/create.ts
738
+ var BRANCH_CREATE_MAX_WAIT_MS = 10 * 60 * 1e3;
739
+ var BRANCH_CREATE_POLL_INTERVAL_MS = 2 * 1e3;
740
+ var BRANCH_CREATE_IN_PROGRESS_DETAIL = "Branch creation is still in progress";
741
+ function branchCreateMaxWaitMs() {
742
+ return Number(process.env.ARDENT_BRANCH_CREATE_MAX_WAIT_MS) || BRANCH_CREATE_MAX_WAIT_MS;
743
+ }
744
+ function branchCreatePollIntervalMs() {
745
+ return Number(process.env.ARDENT_BRANCH_CREATE_POLL_INTERVAL_MS) || BRANCH_CREATE_POLL_INTERVAL_MS;
746
+ }
747
+ var BRANCH_CREATE_TRANSIENT_WARN_EVERY = 10;
748
+ function asBranchCreateWarning(value) {
749
+ if (value && typeof value === "object" && value.type === "stale_source" && typeof value.message === "string") {
750
+ return value;
751
+ }
752
+ return void 0;
753
+ }
754
+ function isBranchCreateInProgressError(err) {
755
+ if (!(err instanceof Error)) {
756
+ return false;
757
+ }
758
+ return err.message.includes("API error 409") && err.message.includes(BRANCH_CREATE_IN_PROGRESS_DETAIL);
759
+ }
662
760
  async function createAction(name, options) {
663
761
  const modeResolution = resolveOutputMode(options);
664
762
  if (modeResolution.error) {
@@ -681,18 +779,26 @@ async function createAction(name, options) {
681
779
  console.error(`\u2717 ${message}`);
682
780
  process.exit(1);
683
781
  }
782
+ let idempotencyScopeKey;
684
783
  try {
685
784
  const startTime = performance.now();
686
785
  const connectorId = await resolveCurrentConnectorId();
687
- const createResponse = await api.post(
786
+ idempotencyScopeKey = `${connectorId}:${options.service}:${name}`;
787
+ let idempotencyKey = getPendingBranchCreateKey(idempotencyScopeKey);
788
+ if (!idempotencyKey) {
789
+ idempotencyKey = randomUUID2();
790
+ setPendingBranchCreateKey(idempotencyScopeKey, idempotencyKey);
791
+ }
792
+ const dispatch = await api.post(
688
793
  "/v1/branch/create",
689
794
  {
690
795
  connector_id: connectorId,
691
796
  service_type: options.service,
692
797
  name
693
- }
798
+ },
799
+ { "X-Idempotency-Key": idempotencyKey }
694
800
  );
695
- const warning = createResponse?.warning;
801
+ const warning = await pollBranchCreate(dispatch.operation_id, idempotencyScopeKey, mode);
696
802
  const response = await api.get(`/v1/cli/branches?connector_id=${connectorId}`);
697
803
  const apiBranches = response.branches || [];
698
804
  let apiBranch;
@@ -746,6 +852,7 @@ async function createAction(name, options) {
746
852
  cachedBranches.push(branch);
747
853
  setCacheEntry("branches", cachedBranches);
748
854
  setCurrentBranch(name);
855
+ clearPendingBranchCreateKey(idempotencyScopeKey);
749
856
  const elapsed = ((performance.now() - startTime) / 1e3).toFixed(1);
750
857
  trackEvent("CLI: branch create succeeded", {
751
858
  service_type: options.service,
@@ -783,6 +890,15 @@ ${url}`);
783
890
  printCurrentConnectorSelectionError(err);
784
891
  process.exit(1);
785
892
  }
893
+ if (err instanceof BranchCreateTimeoutError) {
894
+ trackEvent("CLI: branch create failed", { reason: "timeout", output_mode: mode });
895
+ if (mode === "json") {
896
+ process.stdout.write(renderBranchJsonError("timeout", err.message));
897
+ process.exit(1);
898
+ }
899
+ console.error(`\u2717 ${err.message}`);
900
+ process.exit(1);
901
+ }
786
902
  if (isNetworkError(err)) {
787
903
  trackEvent("CLI: branch create failed", { reason: "offline", output_mode: mode });
788
904
  if (mode === "json") {
@@ -794,6 +910,22 @@ ${url}`);
794
910
  console.error("\u2717 Cannot create branch while offline");
795
911
  process.exit(1);
796
912
  }
913
+ if (isBranchCreateInProgressError(err)) {
914
+ trackEvent("CLI: branch create failed", {
915
+ reason: "in_progress",
916
+ output_mode: mode
917
+ });
918
+ const message2 = err instanceof Error ? err.message : String(err);
919
+ if (mode === "json") {
920
+ process.stdout.write(renderBranchJsonError("api_error", message2));
921
+ process.exit(1);
922
+ }
923
+ console.error("\u2717 Failed:", message2);
924
+ process.exit(1);
925
+ }
926
+ if (idempotencyScopeKey) {
927
+ clearPendingBranchCreateKey(idempotencyScopeKey);
928
+ }
797
929
  trackEvent("CLI: branch create failed", { reason: "api_error", output_mode: mode });
798
930
  const message = err instanceof Error ? err.message : String(err);
799
931
  if (mode === "json") {
@@ -804,6 +936,57 @@ ${url}`);
804
936
  process.exit(1);
805
937
  }
806
938
  }
939
+ var BranchCreateTimeoutError = class extends Error {
940
+ constructor(message) {
941
+ super(message);
942
+ this.name = "BranchCreateTimeoutError";
943
+ }
944
+ };
945
+ async function pollBranchCreate(operationId, idempotencyScopeKey, mode) {
946
+ const startedAt = Date.now();
947
+ const maxWaitMs = branchCreateMaxWaitMs();
948
+ const pollIntervalMs = branchCreatePollIntervalMs();
949
+ let lastStage = null;
950
+ let consecutiveTransientFailures = 0;
951
+ let firstPoll = true;
952
+ while (Date.now() - startedAt < maxWaitMs) {
953
+ if (!firstPoll) {
954
+ await new Promise((resolve) => setTimeout(resolve, pollIntervalMs));
955
+ }
956
+ firstPoll = false;
957
+ let op;
958
+ try {
959
+ op = await api.get(`/v1/operations/${operationId}`);
960
+ } catch (pollErr) {
961
+ if (isTransientOperationPollError(pollErr)) {
962
+ consecutiveTransientFailures += 1;
963
+ if (consecutiveTransientFailures % BRANCH_CREATE_TRANSIENT_WARN_EVERY === 0 && mode !== "json") {
964
+ console.warn(
965
+ ` \u26A0 Status check has failed ${consecutiveTransientFailures} times in a row. Branch creation is still running server-side and the CLI will keep waiting.`
966
+ );
967
+ }
968
+ continue;
969
+ }
970
+ throw pollErr;
971
+ }
972
+ consecutiveTransientFailures = 0;
973
+ if (op.stage && op.stage !== lastStage && mode !== "json" && mode !== "print-url") {
974
+ const progressLabel = op.progress != null ? ` (${op.progress}%)` : "";
975
+ console.log(` ${operationStageDisplay(op.stage)}${progressLabel}`);
976
+ lastStage = op.stage;
977
+ }
978
+ if (op.status === "completed") {
979
+ return asBranchCreateWarning(op.result?.warning);
980
+ }
981
+ if (op.status === "failed") {
982
+ clearPendingBranchCreateKey(idempotencyScopeKey);
983
+ throw new Error(op.error ?? "Branch creation failed");
984
+ }
985
+ }
986
+ throw new BranchCreateTimeoutError(
987
+ `Branch creation did not complete within ${Math.round(maxWaitMs / 6e4)} minutes. It may still be running server-side \u2014 do NOT assume it failed. Re-run the same command to resume, or check \`ardent branch list\`. If you contact Ardent support, reference operation id ${operationId}.`
988
+ );
989
+ }
807
990
 
808
991
  // src/commands/branch/list.ts
809
992
  function formatCreatedDate(createdAtIso) {
@@ -1107,52 +1290,6 @@ function printDegradedWarnings(warnings) {
1107
1290
  console.log(" Review this connector with: ardent connector list");
1108
1291
  }
1109
1292
 
1110
- // src/lib/operation_stage_display.ts
1111
- var STAGE_DISPLAY = {
1112
- // engine_setup_worker / postgres_engine_setup
1113
- "dispatched": "Starting",
1114
- "preparing": "Preparing",
1115
- "creating-neon-project": "Provisioning the branch target",
1116
- "preparing-target-databases": "Preparing target databases",
1117
- "deploying-pgstream": "Starting replication",
1118
- "applying-rls": "Applying RLS policies",
1119
- "storing-credentials": "Storing connection credentials",
1120
- "validating": "Validating the branch target",
1121
- // reset_worker / reset_connector
1122
- "resetting": "Resetting",
1123
- "deleting-pgstream": "Stopping replication",
1124
- "rediscovering-source": "Re-checking the source database",
1125
- "resetting-neon-main": "Resetting the branch target",
1126
- "creating-target-schemas": "Recreating target schemas",
1127
- "redeploying-pgstream": "Restarting replication",
1128
- // environment deploy_worker
1129
- "loading_config": "Loading environment configuration",
1130
- "deploying_infrastructure": "Provisioning environment infrastructure",
1131
- "recording_success": "Finalizing environment",
1132
- "cleaning_failed_deploy": "Cleaning up failed environment provisioning",
1133
- // environment destroy_worker
1134
- "deleting_private_links": "Removing private network links",
1135
- "destroying_infrastructure": "Tearing down environment infrastructure",
1136
- "recording_destroy_success": "Finalizing environment teardown",
1137
- // discovery_worker / run_discovery_crawl (ARD-1098)
1138
- "connecting": "Connecting to source",
1139
- "enumerating_databases": "Listing databases",
1140
- "scanning_database": "Scanning schema",
1141
- "writing_schema": "Saving discovered schema",
1142
- "merging_results": "Combining results"
1143
- };
1144
- function humanizeRawStage(_raw) {
1145
- return "Working";
1146
- }
1147
- function operationStageDisplay(stage) {
1148
- if (!stage) return "Working";
1149
- const colonIndex = stage.indexOf(":");
1150
- const base = colonIndex === -1 ? stage : stage.slice(0, colonIndex);
1151
- const suffix = colonIndex === -1 ? "" : stage.slice(colonIndex + 1);
1152
- const label = STAGE_DISPLAY[base] ?? humanizeRawStage(base);
1153
- return suffix ? `${label} (for ${suffix})` : label;
1154
- }
1155
-
1156
1293
  // src/lib/discover.ts
1157
1294
  var DiscoveryTimeoutError = class extends Error {
1158
1295
  constructor(message) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ardent-cli",
3
- "version": "0.0.49",
3
+ "version": "0.0.51",
4
4
  "description": "Git for Data infrastructure",
5
5
  "type": "module",
6
6
  "bin": {