ardent-cli 0.0.43 → 0.0.45
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 +313 -29
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -399,6 +399,16 @@ function findConnectorById(connectors, connectorId) {
|
|
|
399
399
|
}
|
|
400
400
|
return void 0;
|
|
401
401
|
}
|
|
402
|
+
var CurrentConnectorSelectionError = class extends Error {
|
|
403
|
+
code;
|
|
404
|
+
detailLines;
|
|
405
|
+
constructor(code, message, detailLines = []) {
|
|
406
|
+
super(message);
|
|
407
|
+
this.name = "CurrentConnectorSelectionError";
|
|
408
|
+
this.code = code;
|
|
409
|
+
this.detailLines = detailLines;
|
|
410
|
+
}
|
|
411
|
+
};
|
|
402
412
|
function reconcileSelectedConnector(connectors) {
|
|
403
413
|
const currentConnectorId = getConfig("currentConnectorId");
|
|
404
414
|
if (currentConnectorId) {
|
|
@@ -417,13 +427,27 @@ function reconcileSelectedConnector(connectors) {
|
|
|
417
427
|
}
|
|
418
428
|
return void 0;
|
|
419
429
|
}
|
|
420
|
-
|
|
430
|
+
function printCurrentConnectorSelectionError(error) {
|
|
431
|
+
console.error(`\u2717 ${error.message}`);
|
|
432
|
+
for (const detailLine of error.detailLines) {
|
|
433
|
+
console.error(detailLine);
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
async function resolveCurrentConnectorId() {
|
|
421
437
|
const currentProjectId = getConfig("currentProjectId");
|
|
422
438
|
if (!currentProjectId) {
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
439
|
+
throw new CurrentConnectorSelectionError(
|
|
440
|
+
"no_current_project",
|
|
441
|
+
"No current project set. Switch to a project first.",
|
|
442
|
+
[" ardent project list", " ardent project switch <name>"]
|
|
443
|
+
);
|
|
444
|
+
}
|
|
445
|
+
const token = getToken();
|
|
446
|
+
if (!token) {
|
|
447
|
+
throw new CurrentConnectorSelectionError(
|
|
448
|
+
"not_authenticated",
|
|
449
|
+
"Not authenticated. Run: ardent login"
|
|
450
|
+
);
|
|
427
451
|
}
|
|
428
452
|
const result = await api.get(
|
|
429
453
|
`/v1/cli/connectors?project_id=${currentProjectId}`
|
|
@@ -437,15 +461,52 @@ async function requireCurrentConnectorId() {
|
|
|
437
461
|
return selectedConnector.id;
|
|
438
462
|
}
|
|
439
463
|
if (result.connectors.length === 0) {
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
464
|
+
throw new CurrentConnectorSelectionError(
|
|
465
|
+
"no_connectors",
|
|
466
|
+
"No connectors found.",
|
|
467
|
+
[" Create one with: ardent connector create postgresql <url>"]
|
|
468
|
+
);
|
|
469
|
+
}
|
|
470
|
+
throw new CurrentConnectorSelectionError(
|
|
471
|
+
"connector_unavailable",
|
|
472
|
+
"Previously selected connector is no longer available.",
|
|
473
|
+
[
|
|
474
|
+
" Select one of your current connectors:",
|
|
475
|
+
" ardent connector list",
|
|
476
|
+
" ardent connector switch <name>"
|
|
477
|
+
]
|
|
478
|
+
);
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
// src/lib/resource_name_validation.ts
|
|
482
|
+
var RESERVED_SUFFIXES = ["pooler", "readonly", "direct"];
|
|
483
|
+
var MAX_RESOURCE_NAME_LENGTH = 100;
|
|
484
|
+
var ALLOWED_CHARS = /^[a-z0-9-]+$/;
|
|
485
|
+
function validateResourceName(name) {
|
|
486
|
+
const length = name ? Array.from(name).length : 0;
|
|
487
|
+
if (!name || length > MAX_RESOURCE_NAME_LENGTH) {
|
|
488
|
+
throw new Error(
|
|
489
|
+
`Resource name must be 1-${MAX_RESOURCE_NAME_LENGTH} characters, got ${length}`
|
|
490
|
+
);
|
|
491
|
+
}
|
|
492
|
+
if (!ALLOWED_CHARS.test(name)) {
|
|
493
|
+
throw new Error(
|
|
494
|
+
`Resource name must contain only lowercase letters, digits, and hyphens: '${name}'`
|
|
495
|
+
);
|
|
496
|
+
}
|
|
497
|
+
if (name.startsWith("-") || name.endsWith("-")) {
|
|
498
|
+
throw new Error(`Resource name must not start or end with a hyphen: '${name}'`);
|
|
499
|
+
}
|
|
500
|
+
if (name.includes("--")) {
|
|
501
|
+
throw new Error(`Resource name must not contain consecutive hyphens: '${name}'`);
|
|
502
|
+
}
|
|
503
|
+
for (const suffix of RESERVED_SUFFIXES) {
|
|
504
|
+
if (name.endsWith(`-${suffix}`)) {
|
|
505
|
+
throw new Error(
|
|
506
|
+
`Resource name must not end with reserved suffix '-${suffix}': '${name}'`
|
|
507
|
+
);
|
|
508
|
+
}
|
|
447
509
|
}
|
|
448
|
-
process.exit(1);
|
|
449
510
|
}
|
|
450
511
|
|
|
451
512
|
// src/lib/telemetry.ts
|
|
@@ -505,11 +566,115 @@ function identifyUser(userId, personProperties = {}) {
|
|
|
505
566
|
}).finally(() => clearTimeout(timeout));
|
|
506
567
|
}
|
|
507
568
|
|
|
569
|
+
// src/lib/branch_output.ts
|
|
570
|
+
var BRANCH_JSON_SCHEMA_VERSION = 1;
|
|
571
|
+
function buildBranchJson({
|
|
572
|
+
branch,
|
|
573
|
+
warning = null,
|
|
574
|
+
currentBranchName
|
|
575
|
+
}) {
|
|
576
|
+
return {
|
|
577
|
+
schema_version: BRANCH_JSON_SCHEMA_VERSION,
|
|
578
|
+
id: branch.id,
|
|
579
|
+
name: branch.name,
|
|
580
|
+
connector_id: branch.connector_id,
|
|
581
|
+
service_type: branch.service_type,
|
|
582
|
+
status: branch.status,
|
|
583
|
+
branch_url: resolvePrintableUrl(branch),
|
|
584
|
+
created_at: branch.created_at,
|
|
585
|
+
last_branch_activity: branch.last_branch_activity ?? null,
|
|
586
|
+
current: currentBranchName !== void 0 && currentBranchName === branch.name,
|
|
587
|
+
warning
|
|
588
|
+
};
|
|
589
|
+
}
|
|
590
|
+
function renderBranchJson(payload) {
|
|
591
|
+
return JSON.stringify(payload) + "\n";
|
|
592
|
+
}
|
|
593
|
+
function renderBranchJsonError(code, message) {
|
|
594
|
+
const envelope = {
|
|
595
|
+
schema_version: BRANCH_JSON_SCHEMA_VERSION,
|
|
596
|
+
error: { code, message }
|
|
597
|
+
};
|
|
598
|
+
return JSON.stringify(envelope) + "\n";
|
|
599
|
+
}
|
|
600
|
+
function resolveOutputMode(options) {
|
|
601
|
+
const hasFormat = options.format !== void 0;
|
|
602
|
+
const hasPrintUrl = options.printUrl === true;
|
|
603
|
+
if (hasFormat && hasPrintUrl) {
|
|
604
|
+
return {
|
|
605
|
+
mode: "pretty",
|
|
606
|
+
error: "--format and --print-url are mutually exclusive"
|
|
607
|
+
};
|
|
608
|
+
}
|
|
609
|
+
if (hasFormat) {
|
|
610
|
+
if (options.format !== "json") {
|
|
611
|
+
return {
|
|
612
|
+
mode: "pretty",
|
|
613
|
+
error: `Unsupported --format value: ${options.format}. Supported: json`
|
|
614
|
+
};
|
|
615
|
+
}
|
|
616
|
+
return { mode: "json" };
|
|
617
|
+
}
|
|
618
|
+
if (hasPrintUrl) {
|
|
619
|
+
return { mode: "print-url" };
|
|
620
|
+
}
|
|
621
|
+
return { mode: "pretty" };
|
|
622
|
+
}
|
|
623
|
+
function resolvePrintableUrl(branch) {
|
|
624
|
+
if (!branch.branch_url) {
|
|
625
|
+
return null;
|
|
626
|
+
}
|
|
627
|
+
return branch.branch_url;
|
|
628
|
+
}
|
|
629
|
+
function isMachineReadableBranchInvocation(args2) {
|
|
630
|
+
let branchIndex = -1;
|
|
631
|
+
for (let index = 0; index < args2.length; index += 1) {
|
|
632
|
+
if (args2[index] === "branch") {
|
|
633
|
+
branchIndex = index;
|
|
634
|
+
break;
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
if (branchIndex === -1) {
|
|
638
|
+
return false;
|
|
639
|
+
}
|
|
640
|
+
const branchSubcommand = args2[branchIndex + 1];
|
|
641
|
+
if (branchSubcommand !== "create" && branchSubcommand !== "info") {
|
|
642
|
+
return false;
|
|
643
|
+
}
|
|
644
|
+
for (const arg of args2.slice(branchIndex + 2)) {
|
|
645
|
+
if (arg === "--print-url" || arg === "--format" || arg.startsWith("--format=")) {
|
|
646
|
+
return true;
|
|
647
|
+
}
|
|
648
|
+
}
|
|
649
|
+
return false;
|
|
650
|
+
}
|
|
651
|
+
|
|
508
652
|
// src/commands/branch/create.ts
|
|
509
653
|
async function createAction(name, options) {
|
|
654
|
+
const modeResolution = resolveOutputMode(options);
|
|
655
|
+
if (modeResolution.error) {
|
|
656
|
+
console.error(`\u2717 ${modeResolution.error}`);
|
|
657
|
+
process.exit(2);
|
|
658
|
+
}
|
|
659
|
+
const mode = modeResolution.mode;
|
|
660
|
+
try {
|
|
661
|
+
validateResourceName(name);
|
|
662
|
+
} catch (validationError) {
|
|
663
|
+
const message = validationError instanceof Error ? validationError.message : String(validationError);
|
|
664
|
+
trackEvent("CLI: branch create failed", {
|
|
665
|
+
reason: "invalid_name",
|
|
666
|
+
output_mode: mode
|
|
667
|
+
});
|
|
668
|
+
if (mode === "json") {
|
|
669
|
+
process.stdout.write(renderBranchJsonError("invalid_name", message));
|
|
670
|
+
process.exit(1);
|
|
671
|
+
}
|
|
672
|
+
console.error(`\u2717 ${message}`);
|
|
673
|
+
process.exit(1);
|
|
674
|
+
}
|
|
510
675
|
try {
|
|
511
676
|
const startTime = performance.now();
|
|
512
|
-
const connectorId = await
|
|
677
|
+
const connectorId = await resolveCurrentConnectorId();
|
|
513
678
|
const createResponse = await api.post(
|
|
514
679
|
"/v1/branch/create",
|
|
515
680
|
{
|
|
@@ -529,7 +694,18 @@ async function createAction(name, options) {
|
|
|
529
694
|
}
|
|
530
695
|
}
|
|
531
696
|
if (!apiBranch) {
|
|
532
|
-
|
|
697
|
+
const message = "Branch created but could not fetch details";
|
|
698
|
+
trackEvent("CLI: branch create failed", {
|
|
699
|
+
reason: "create_succeeded_but_details_missing",
|
|
700
|
+
output_mode: mode
|
|
701
|
+
});
|
|
702
|
+
if (mode === "json") {
|
|
703
|
+
process.stdout.write(
|
|
704
|
+
renderBranchJsonError("create_succeeded_but_details_missing", message)
|
|
705
|
+
);
|
|
706
|
+
process.exit(1);
|
|
707
|
+
}
|
|
708
|
+
console.error(`\u2717 ${message}`);
|
|
533
709
|
process.exit(1);
|
|
534
710
|
}
|
|
535
711
|
const branch = {
|
|
@@ -542,6 +718,20 @@ async function createAction(name, options) {
|
|
|
542
718
|
created_at: apiBranch.created_at,
|
|
543
719
|
last_branch_activity: apiBranch.last_branch_activity
|
|
544
720
|
};
|
|
721
|
+
const url = resolvePrintableUrl(branch);
|
|
722
|
+
if (url === null) {
|
|
723
|
+
const message = `Branch '${name}' created but no URL returned`;
|
|
724
|
+
trackEvent("CLI: branch create failed", {
|
|
725
|
+
reason: "branch_url_missing",
|
|
726
|
+
output_mode: mode
|
|
727
|
+
});
|
|
728
|
+
if (mode === "json") {
|
|
729
|
+
process.stdout.write(renderBranchJsonError("branch_url_missing", message));
|
|
730
|
+
process.exit(1);
|
|
731
|
+
}
|
|
732
|
+
console.error(`\u2717 ${message}`);
|
|
733
|
+
process.exit(1);
|
|
734
|
+
}
|
|
545
735
|
const cached = getCacheEntry("branches");
|
|
546
736
|
const cachedBranches = cached?.data || [];
|
|
547
737
|
cachedBranches.push(branch);
|
|
@@ -551,25 +741,57 @@ async function createAction(name, options) {
|
|
|
551
741
|
trackEvent("CLI: branch create succeeded", {
|
|
552
742
|
service_type: options.service,
|
|
553
743
|
duration_seconds: parseFloat(elapsed),
|
|
554
|
-
warning_type: warning?.type
|
|
744
|
+
warning_type: warning?.type,
|
|
745
|
+
output_mode: mode
|
|
555
746
|
});
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
747
|
+
if (mode === "json") {
|
|
748
|
+
const payload = buildBranchJson({
|
|
749
|
+
branch,
|
|
750
|
+
warning: warning ?? null,
|
|
751
|
+
currentBranchName: name
|
|
752
|
+
});
|
|
753
|
+
process.stdout.write(renderBranchJson(payload));
|
|
754
|
+
return;
|
|
755
|
+
}
|
|
756
|
+
if (mode === "print-url") {
|
|
757
|
+
process.stdout.write(url + "\n");
|
|
758
|
+
return;
|
|
560
759
|
}
|
|
760
|
+
console.log(`\u2713 Branch '${name}' created and checked out in ${elapsed}s`);
|
|
761
|
+
console.log(`
|
|
762
|
+
${url}`);
|
|
561
763
|
if (warning) {
|
|
562
764
|
console.log(`
|
|
563
765
|
! ${warning.message}`);
|
|
564
766
|
}
|
|
565
767
|
} catch (err) {
|
|
768
|
+
if (err instanceof CurrentConnectorSelectionError) {
|
|
769
|
+
trackEvent("CLI: branch create failed", { reason: err.code, output_mode: mode });
|
|
770
|
+
if (mode === "json") {
|
|
771
|
+
process.stdout.write(renderBranchJsonError(err.code, err.message));
|
|
772
|
+
process.exit(1);
|
|
773
|
+
}
|
|
774
|
+
printCurrentConnectorSelectionError(err);
|
|
775
|
+
process.exit(1);
|
|
776
|
+
}
|
|
566
777
|
if (isNetworkError(err)) {
|
|
567
|
-
trackEvent("CLI: branch create failed", { reason: "offline" });
|
|
778
|
+
trackEvent("CLI: branch create failed", { reason: "offline", output_mode: mode });
|
|
779
|
+
if (mode === "json") {
|
|
780
|
+
process.stdout.write(
|
|
781
|
+
renderBranchJsonError("offline", "Cannot create branch while offline")
|
|
782
|
+
);
|
|
783
|
+
process.exit(1);
|
|
784
|
+
}
|
|
568
785
|
console.error("\u2717 Cannot create branch while offline");
|
|
569
786
|
process.exit(1);
|
|
570
787
|
}
|
|
571
|
-
trackEvent("CLI: branch create failed", { reason: "api_error" });
|
|
572
|
-
|
|
788
|
+
trackEvent("CLI: branch create failed", { reason: "api_error", output_mode: mode });
|
|
789
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
790
|
+
if (mode === "json") {
|
|
791
|
+
process.stdout.write(renderBranchJsonError("api_error", message));
|
|
792
|
+
process.exit(1);
|
|
793
|
+
}
|
|
794
|
+
console.error("\u2717 Failed:", message);
|
|
573
795
|
process.exit(1);
|
|
574
796
|
}
|
|
575
797
|
}
|
|
@@ -650,9 +872,28 @@ async function listAction() {
|
|
|
650
872
|
}
|
|
651
873
|
|
|
652
874
|
// src/commands/branch/info.ts
|
|
653
|
-
function infoAction(name) {
|
|
875
|
+
function infoAction(name, options = {}) {
|
|
876
|
+
const modeResolution = resolveOutputMode(options);
|
|
877
|
+
if (modeResolution.error) {
|
|
878
|
+
console.error(`\u2717 ${modeResolution.error}`);
|
|
879
|
+
process.exit(2);
|
|
880
|
+
}
|
|
881
|
+
const mode = modeResolution.mode;
|
|
654
882
|
const branchName = name || getCurrentBranch();
|
|
655
883
|
if (!branchName) {
|
|
884
|
+
if (mode === "json") {
|
|
885
|
+
process.stdout.write(
|
|
886
|
+
renderBranchJsonError(
|
|
887
|
+
"no_current_branch",
|
|
888
|
+
"No branch specified and no current branch set"
|
|
889
|
+
)
|
|
890
|
+
);
|
|
891
|
+
process.exit(1);
|
|
892
|
+
}
|
|
893
|
+
if (mode === "print-url") {
|
|
894
|
+
console.error("\u2717 No branch specified and no current branch set");
|
|
895
|
+
process.exit(1);
|
|
896
|
+
}
|
|
656
897
|
console.error("\u2717 No branch specified and no current branch set");
|
|
657
898
|
console.log(" Run: ardent branch switch <name> or specify a branch name");
|
|
658
899
|
return;
|
|
@@ -660,10 +901,52 @@ function infoAction(name) {
|
|
|
660
901
|
const cached = getCacheEntry("branches");
|
|
661
902
|
const branch = cached?.data.find((cachedBranch) => cachedBranch.name === branchName);
|
|
662
903
|
if (!branch) {
|
|
663
|
-
|
|
904
|
+
const message = `Branch "${branchName}" not found`;
|
|
905
|
+
if (mode === "json") {
|
|
906
|
+
process.stdout.write(renderBranchJsonError("not_found", message));
|
|
907
|
+
process.exit(1);
|
|
908
|
+
}
|
|
909
|
+
if (mode === "print-url") {
|
|
910
|
+
console.error(`\u2717 ${message}`);
|
|
911
|
+
process.exit(1);
|
|
912
|
+
}
|
|
913
|
+
console.error(`\u2717 ${message}`);
|
|
664
914
|
console.log(" Run: ardent branch list");
|
|
665
915
|
return;
|
|
666
916
|
}
|
|
917
|
+
if (mode === "json" || mode === "print-url") {
|
|
918
|
+
const url = resolvePrintableUrl(branch);
|
|
919
|
+
if (url === null) {
|
|
920
|
+
const message = `No URL recorded for branch "${branch.name}"`;
|
|
921
|
+
trackEvent("CLI: branch info failed", {
|
|
922
|
+
reason: "branch_url_missing",
|
|
923
|
+
output_mode: mode
|
|
924
|
+
});
|
|
925
|
+
if (mode === "json") {
|
|
926
|
+
process.stdout.write(renderBranchJsonError("branch_url_missing", message));
|
|
927
|
+
process.exit(1);
|
|
928
|
+
}
|
|
929
|
+
console.error(`\u2717 ${message}`);
|
|
930
|
+
process.exit(1);
|
|
931
|
+
}
|
|
932
|
+
}
|
|
933
|
+
trackEvent("CLI: branch info", { output_mode: mode });
|
|
934
|
+
if (mode === "json") {
|
|
935
|
+
const payload = buildBranchJson({
|
|
936
|
+
branch,
|
|
937
|
+
currentBranchName: getCurrentBranch()
|
|
938
|
+
});
|
|
939
|
+
process.stdout.write(renderBranchJson(payload));
|
|
940
|
+
return;
|
|
941
|
+
}
|
|
942
|
+
if (mode === "print-url") {
|
|
943
|
+
const url = resolvePrintableUrl(branch);
|
|
944
|
+
if (url === null) {
|
|
945
|
+
throw new Error("Branch URL disappeared after validation");
|
|
946
|
+
}
|
|
947
|
+
process.stdout.write(url + "\n");
|
|
948
|
+
return;
|
|
949
|
+
}
|
|
667
950
|
const current = getCurrentBranch();
|
|
668
951
|
const marker = branch.name === current ? " (current)" : "";
|
|
669
952
|
console.log(`Branch: ${branch.name}${marker}
|
|
@@ -675,7 +958,6 @@ function infoAction(name) {
|
|
|
675
958
|
console.log(`
|
|
676
959
|
URL: ${branch.branch_url}`);
|
|
677
960
|
}
|
|
678
|
-
trackEvent("CLI: branch info");
|
|
679
961
|
}
|
|
680
962
|
|
|
681
963
|
// src/commands/branch/delete.ts
|
|
@@ -770,9 +1052,9 @@ ${yellow}To view the current state of a branch:${reset2} \`ardent branch info <n
|
|
|
770
1052
|
|
|
771
1053
|
// src/commands/branch/index.ts
|
|
772
1054
|
var branchCommand = new Command("branch").description("Manage database branches");
|
|
773
|
-
branchCommand.command("create <name>").description("Create a new database branch").option("-s, --service <type>", "Service type", "postgres").action(createAction);
|
|
1055
|
+
branchCommand.command("create <name>").description("Create a new database branch").option("-s, --service <type>", "Service type", "postgres").option("--format <format>", "Output format for CI/agents (currently: json)").option("--print-url", "Print only the branch URL (one line, no other output)").action(createAction);
|
|
774
1056
|
branchCommand.command("list").description("List your branches").action(listAction);
|
|
775
|
-
branchCommand.command("info [name]").description("Show branch details (defaults to current branch)").action(infoAction);
|
|
1057
|
+
branchCommand.command("info [name]").description("Show branch details (defaults to current branch)").option("--format <format>", "Output format for CI/agents (currently: json)").option("--print-url", "Print only the branch URL (one line, no other output)").action(infoAction);
|
|
776
1058
|
branchCommand.command("delete <name>").description("Delete a branch").action(deleteAction);
|
|
777
1059
|
branchCommand.command("switch <name>").description("Switch to a different branch").action(switchAction);
|
|
778
1060
|
branchCommand.command("diff [name]", { hidden: true }).description("(removed) Show changes on a branch").action(diffAction);
|
|
@@ -4851,5 +5133,7 @@ if (args.length === 0) {
|
|
|
4851
5133
|
program.help();
|
|
4852
5134
|
}
|
|
4853
5135
|
getAnonymousId();
|
|
4854
|
-
|
|
5136
|
+
if (!isMachineReadableBranchInvocation(args)) {
|
|
5137
|
+
checkForUpdate(CLI_VERSION);
|
|
5138
|
+
}
|
|
4855
5139
|
program.parse();
|