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.
- package/dist/index.js +188 -51
- 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:
|
|
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
|
-
|
|
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 =
|
|
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) {
|