ardent-cli 0.0.48 → 0.0.50

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 +65 -7
  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);
@@ -384,6 +408,15 @@ function isGatewayTimeoutError(err) {
384
408
  }
385
409
  return isNetworkError(err);
386
410
  }
411
+ function isTransientOperationPollError(err) {
412
+ if (isGatewayTimeoutError(err)) {
413
+ return true;
414
+ }
415
+ if (!(err instanceof Error)) {
416
+ return false;
417
+ }
418
+ return err.message.trim().toLowerCase() === "api error 500: internal server error";
419
+ }
387
420
 
388
421
  // src/lib/connector_selection.ts
389
422
  function clearSelectedConnector() {
@@ -672,16 +705,24 @@ async function createAction(name, options) {
672
705
  console.error(`\u2717 ${message}`);
673
706
  process.exit(1);
674
707
  }
708
+ let idempotencyScopeKey;
675
709
  try {
676
710
  const startTime = performance.now();
677
711
  const connectorId = await resolveCurrentConnectorId();
712
+ idempotencyScopeKey = `${connectorId}:${options.service}:${name}`;
713
+ let idempotencyKey = getPendingBranchCreateKey(idempotencyScopeKey);
714
+ if (!idempotencyKey) {
715
+ idempotencyKey = randomUUID2();
716
+ setPendingBranchCreateKey(idempotencyScopeKey, idempotencyKey);
717
+ }
678
718
  const createResponse = await api.post(
679
719
  "/v1/branch/create",
680
720
  {
681
721
  connector_id: connectorId,
682
722
  service_type: options.service,
683
723
  name
684
- }
724
+ },
725
+ { "X-Idempotency-Key": idempotencyKey }
685
726
  );
686
727
  const warning = createResponse?.warning;
687
728
  const response = await api.get(`/v1/cli/branches?connector_id=${connectorId}`);
@@ -737,6 +778,7 @@ async function createAction(name, options) {
737
778
  cachedBranches.push(branch);
738
779
  setCacheEntry("branches", cachedBranches);
739
780
  setCurrentBranch(name);
781
+ clearPendingBranchCreateKey(idempotencyScopeKey);
740
782
  const elapsed = ((performance.now() - startTime) / 1e3).toFixed(1);
741
783
  trackEvent("CLI: branch create succeeded", {
742
784
  service_type: options.service,
@@ -785,6 +827,9 @@ ${url}`);
785
827
  console.error("\u2717 Cannot create branch while offline");
786
828
  process.exit(1);
787
829
  }
830
+ if (idempotencyScopeKey) {
831
+ clearPendingBranchCreateKey(idempotencyScopeKey);
832
+ }
788
833
  trackEvent("CLI: branch create failed", { reason: "api_error", output_mode: mode });
789
834
  const message = err instanceof Error ? err.message : String(err);
790
835
  if (mode === "json") {
@@ -1306,6 +1351,9 @@ function assertEngineSetupCompleted(operation, connectorName) {
1306
1351
  }
1307
1352
 
1308
1353
  // src/lib/engine_setup.ts
1354
+ var ENGINE_SETUP_MAX_WAIT_MS = 60 * 60 * 1e3;
1355
+ var ENGINE_SETUP_POLL_INTERVAL_MS = 5 * 1e3;
1356
+ var ENGINE_SETUP_TRANSIENT_WARN_EVERY = 6;
1309
1357
  var EngineSetupTimeoutError = class extends Error {
1310
1358
  constructor(message) {
1311
1359
  super(message);
@@ -1322,7 +1370,7 @@ var EngineSetupOperationFailedError = class extends Error {
1322
1370
  this.operationError = operationError;
1323
1371
  }
1324
1372
  };
1325
- async function runEngineSetupWithPolling(connectorId, connectorName) {
1373
+ async function runEngineSetupWithPolling(connectorId, connectorName, options = {}) {
1326
1374
  let dispatch;
1327
1375
  try {
1328
1376
  dispatch = await api.post(
@@ -1342,18 +1390,28 @@ async function runEngineSetupWithPolling(connectorId, connectorName) {
1342
1390
  return { dispatched: false };
1343
1391
  }
1344
1392
  const startedAt = Date.now();
1345
- const maxWaitMs = 20 * 60 * 1e3;
1346
- const pollIntervalMs = 5 * 1e3;
1393
+ const maxWaitMs = options.maxWaitMs ?? ENGINE_SETUP_MAX_WAIT_MS;
1394
+ const pollIntervalMs = options.pollIntervalMs ?? ENGINE_SETUP_POLL_INTERVAL_MS;
1347
1395
  let lastStage = null;
1396
+ let consecutiveTransientFailures = 0;
1348
1397
  while (Date.now() - startedAt < maxWaitMs) {
1349
1398
  await new Promise((resolve) => setTimeout(resolve, pollIntervalMs));
1350
1399
  let op;
1351
1400
  try {
1352
1401
  op = await api.get(`/v1/operations/${operationId}`);
1353
1402
  } catch (pollErr) {
1354
- if (isGatewayTimeoutError(pollErr)) continue;
1403
+ if (isTransientOperationPollError(pollErr)) {
1404
+ consecutiveTransientFailures += 1;
1405
+ if (consecutiveTransientFailures % ENGINE_SETUP_TRANSIENT_WARN_EVERY === 0) {
1406
+ console.warn(
1407
+ ` \u26A0 Status check has failed ${consecutiveTransientFailures} times in a row. Setup is still running server-side and the CLI will keep waiting \u2014 do NOT delete the connector.`
1408
+ );
1409
+ }
1410
+ continue;
1411
+ }
1355
1412
  throw pollErr;
1356
1413
  }
1414
+ consecutiveTransientFailures = 0;
1357
1415
  if (op.stage && op.stage !== lastStage) {
1358
1416
  const progressLabel = op.progress != null ? ` (${op.progress}%)` : "";
1359
1417
  console.log(` ${operationStageDisplay(op.stage)}${progressLabel}`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ardent-cli",
3
- "version": "0.0.48",
3
+ "version": "0.0.50",
4
4
  "description": "Git for Data infrastructure",
5
5
  "type": "module",
6
6
  "bin": {