ardent-cli 0.0.42 → 0.0.44

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 +296 -32
  2. 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
- async function requireCurrentConnectorId() {
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
- console.error("\u2717 No current project set. Switch to a project first:");
424
- console.error(" ardent project list");
425
- console.error(" ardent project switch <name>");
426
- process.exit(1);
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,21 @@ async function requireCurrentConnectorId() {
437
461
  return selectedConnector.id;
438
462
  }
439
463
  if (result.connectors.length === 0) {
440
- console.error("\u2717 No connectors found.");
441
- console.error(" Create one with: ardent connector create postgresql <url>");
442
- } else {
443
- console.error("\u2717 Previously selected connector is no longer available.");
444
- console.error(" Select one of your current connectors:");
445
- console.error(" ardent connector list");
446
- console.error(" ardent connector switch <name>");
464
+ throw new CurrentConnectorSelectionError(
465
+ "no_connectors",
466
+ "No connectors found.",
467
+ [" Create one with: ardent connector create postgresql <url>"]
468
+ );
447
469
  }
448
- process.exit(1);
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
+ );
449
479
  }
450
480
 
451
481
  // src/lib/telemetry.ts
@@ -505,11 +535,100 @@ function identifyUser(userId, personProperties = {}) {
505
535
  }).finally(() => clearTimeout(timeout));
506
536
  }
507
537
 
538
+ // src/lib/branch_output.ts
539
+ var BRANCH_JSON_SCHEMA_VERSION = 1;
540
+ function buildBranchJson({
541
+ branch,
542
+ warning = null,
543
+ currentBranchName
544
+ }) {
545
+ return {
546
+ schema_version: BRANCH_JSON_SCHEMA_VERSION,
547
+ id: branch.id,
548
+ name: branch.name,
549
+ connector_id: branch.connector_id,
550
+ service_type: branch.service_type,
551
+ status: branch.status,
552
+ branch_url: resolvePrintableUrl(branch),
553
+ created_at: branch.created_at,
554
+ last_branch_activity: branch.last_branch_activity ?? null,
555
+ current: currentBranchName !== void 0 && currentBranchName === branch.name,
556
+ warning
557
+ };
558
+ }
559
+ function renderBranchJson(payload) {
560
+ return JSON.stringify(payload) + "\n";
561
+ }
562
+ function renderBranchJsonError(code, message) {
563
+ const envelope = {
564
+ schema_version: BRANCH_JSON_SCHEMA_VERSION,
565
+ error: { code, message }
566
+ };
567
+ return JSON.stringify(envelope) + "\n";
568
+ }
569
+ function resolveOutputMode(options) {
570
+ const hasFormat = options.format !== void 0;
571
+ const hasPrintUrl = options.printUrl === true;
572
+ if (hasFormat && hasPrintUrl) {
573
+ return {
574
+ mode: "pretty",
575
+ error: "--format and --print-url are mutually exclusive"
576
+ };
577
+ }
578
+ if (hasFormat) {
579
+ if (options.format !== "json") {
580
+ return {
581
+ mode: "pretty",
582
+ error: `Unsupported --format value: ${options.format}. Supported: json`
583
+ };
584
+ }
585
+ return { mode: "json" };
586
+ }
587
+ if (hasPrintUrl) {
588
+ return { mode: "print-url" };
589
+ }
590
+ return { mode: "pretty" };
591
+ }
592
+ function resolvePrintableUrl(branch) {
593
+ if (!branch.branch_url) {
594
+ return null;
595
+ }
596
+ return branch.branch_url;
597
+ }
598
+ function isMachineReadableBranchInvocation(args2) {
599
+ let branchIndex = -1;
600
+ for (let index = 0; index < args2.length; index += 1) {
601
+ if (args2[index] === "branch") {
602
+ branchIndex = index;
603
+ break;
604
+ }
605
+ }
606
+ if (branchIndex === -1) {
607
+ return false;
608
+ }
609
+ const branchSubcommand = args2[branchIndex + 1];
610
+ if (branchSubcommand !== "create" && branchSubcommand !== "info") {
611
+ return false;
612
+ }
613
+ for (const arg of args2.slice(branchIndex + 2)) {
614
+ if (arg === "--print-url" || arg === "--format" || arg.startsWith("--format=")) {
615
+ return true;
616
+ }
617
+ }
618
+ return false;
619
+ }
620
+
508
621
  // src/commands/branch/create.ts
509
622
  async function createAction(name, options) {
623
+ const modeResolution = resolveOutputMode(options);
624
+ if (modeResolution.error) {
625
+ console.error(`\u2717 ${modeResolution.error}`);
626
+ process.exit(2);
627
+ }
628
+ const mode = modeResolution.mode;
510
629
  try {
511
630
  const startTime = performance.now();
512
- const connectorId = await requireCurrentConnectorId();
631
+ const connectorId = await resolveCurrentConnectorId();
513
632
  const createResponse = await api.post(
514
633
  "/v1/branch/create",
515
634
  {
@@ -529,7 +648,18 @@ async function createAction(name, options) {
529
648
  }
530
649
  }
531
650
  if (!apiBranch) {
532
- console.error("\u2717 Branch created but could not fetch details");
651
+ const message = "Branch created but could not fetch details";
652
+ trackEvent("CLI: branch create failed", {
653
+ reason: "create_succeeded_but_details_missing",
654
+ output_mode: mode
655
+ });
656
+ if (mode === "json") {
657
+ process.stdout.write(
658
+ renderBranchJsonError("create_succeeded_but_details_missing", message)
659
+ );
660
+ process.exit(1);
661
+ }
662
+ console.error(`\u2717 ${message}`);
533
663
  process.exit(1);
534
664
  }
535
665
  const branch = {
@@ -542,6 +672,20 @@ async function createAction(name, options) {
542
672
  created_at: apiBranch.created_at,
543
673
  last_branch_activity: apiBranch.last_branch_activity
544
674
  };
675
+ const url = resolvePrintableUrl(branch);
676
+ if (url === null) {
677
+ const message = `Branch '${name}' created but no URL returned`;
678
+ trackEvent("CLI: branch create failed", {
679
+ reason: "branch_url_missing",
680
+ output_mode: mode
681
+ });
682
+ if (mode === "json") {
683
+ process.stdout.write(renderBranchJsonError("branch_url_missing", message));
684
+ process.exit(1);
685
+ }
686
+ console.error(`\u2717 ${message}`);
687
+ process.exit(1);
688
+ }
545
689
  const cached = getCacheEntry("branches");
546
690
  const cachedBranches = cached?.data || [];
547
691
  cachedBranches.push(branch);
@@ -551,25 +695,57 @@ async function createAction(name, options) {
551
695
  trackEvent("CLI: branch create succeeded", {
552
696
  service_type: options.service,
553
697
  duration_seconds: parseFloat(elapsed),
554
- warning_type: warning?.type
698
+ warning_type: warning?.type,
699
+ output_mode: mode
555
700
  });
556
- console.log(`\u2713 Branch '${name}' created and checked out in ${elapsed}s`);
557
- if (branch.branch_url) {
558
- console.log(`
559
- ${branch.branch_url}`);
701
+ if (mode === "json") {
702
+ const payload = buildBranchJson({
703
+ branch,
704
+ warning: warning ?? null,
705
+ currentBranchName: name
706
+ });
707
+ process.stdout.write(renderBranchJson(payload));
708
+ return;
709
+ }
710
+ if (mode === "print-url") {
711
+ process.stdout.write(url + "\n");
712
+ return;
560
713
  }
714
+ console.log(`\u2713 Branch '${name}' created and checked out in ${elapsed}s`);
715
+ console.log(`
716
+ ${url}`);
561
717
  if (warning) {
562
718
  console.log(`
563
719
  ! ${warning.message}`);
564
720
  }
565
721
  } catch (err) {
722
+ if (err instanceof CurrentConnectorSelectionError) {
723
+ trackEvent("CLI: branch create failed", { reason: err.code, output_mode: mode });
724
+ if (mode === "json") {
725
+ process.stdout.write(renderBranchJsonError(err.code, err.message));
726
+ process.exit(1);
727
+ }
728
+ printCurrentConnectorSelectionError(err);
729
+ process.exit(1);
730
+ }
566
731
  if (isNetworkError(err)) {
567
- trackEvent("CLI: branch create failed", { reason: "offline" });
732
+ trackEvent("CLI: branch create failed", { reason: "offline", output_mode: mode });
733
+ if (mode === "json") {
734
+ process.stdout.write(
735
+ renderBranchJsonError("offline", "Cannot create branch while offline")
736
+ );
737
+ process.exit(1);
738
+ }
568
739
  console.error("\u2717 Cannot create branch while offline");
569
740
  process.exit(1);
570
741
  }
571
- trackEvent("CLI: branch create failed", { reason: "api_error" });
572
- console.error("\u2717 Failed:", err instanceof Error ? err.message : err);
742
+ trackEvent("CLI: branch create failed", { reason: "api_error", output_mode: mode });
743
+ const message = err instanceof Error ? err.message : String(err);
744
+ if (mode === "json") {
745
+ process.stdout.write(renderBranchJsonError("api_error", message));
746
+ process.exit(1);
747
+ }
748
+ console.error("\u2717 Failed:", message);
573
749
  process.exit(1);
574
750
  }
575
751
  }
@@ -650,9 +826,28 @@ async function listAction() {
650
826
  }
651
827
 
652
828
  // src/commands/branch/info.ts
653
- function infoAction(name) {
829
+ function infoAction(name, options = {}) {
830
+ const modeResolution = resolveOutputMode(options);
831
+ if (modeResolution.error) {
832
+ console.error(`\u2717 ${modeResolution.error}`);
833
+ process.exit(2);
834
+ }
835
+ const mode = modeResolution.mode;
654
836
  const branchName = name || getCurrentBranch();
655
837
  if (!branchName) {
838
+ if (mode === "json") {
839
+ process.stdout.write(
840
+ renderBranchJsonError(
841
+ "no_current_branch",
842
+ "No branch specified and no current branch set"
843
+ )
844
+ );
845
+ process.exit(1);
846
+ }
847
+ if (mode === "print-url") {
848
+ console.error("\u2717 No branch specified and no current branch set");
849
+ process.exit(1);
850
+ }
656
851
  console.error("\u2717 No branch specified and no current branch set");
657
852
  console.log(" Run: ardent branch switch <name> or specify a branch name");
658
853
  return;
@@ -660,10 +855,52 @@ function infoAction(name) {
660
855
  const cached = getCacheEntry("branches");
661
856
  const branch = cached?.data.find((cachedBranch) => cachedBranch.name === branchName);
662
857
  if (!branch) {
663
- console.error(`\u2717 Branch "${branchName}" not found`);
858
+ const message = `Branch "${branchName}" not found`;
859
+ if (mode === "json") {
860
+ process.stdout.write(renderBranchJsonError("not_found", message));
861
+ process.exit(1);
862
+ }
863
+ if (mode === "print-url") {
864
+ console.error(`\u2717 ${message}`);
865
+ process.exit(1);
866
+ }
867
+ console.error(`\u2717 ${message}`);
664
868
  console.log(" Run: ardent branch list");
665
869
  return;
666
870
  }
871
+ if (mode === "json" || mode === "print-url") {
872
+ const url = resolvePrintableUrl(branch);
873
+ if (url === null) {
874
+ const message = `No URL recorded for branch "${branch.name}"`;
875
+ trackEvent("CLI: branch info failed", {
876
+ reason: "branch_url_missing",
877
+ output_mode: mode
878
+ });
879
+ if (mode === "json") {
880
+ process.stdout.write(renderBranchJsonError("branch_url_missing", message));
881
+ process.exit(1);
882
+ }
883
+ console.error(`\u2717 ${message}`);
884
+ process.exit(1);
885
+ }
886
+ }
887
+ trackEvent("CLI: branch info", { output_mode: mode });
888
+ if (mode === "json") {
889
+ const payload = buildBranchJson({
890
+ branch,
891
+ currentBranchName: getCurrentBranch()
892
+ });
893
+ process.stdout.write(renderBranchJson(payload));
894
+ return;
895
+ }
896
+ if (mode === "print-url") {
897
+ const url = resolvePrintableUrl(branch);
898
+ if (url === null) {
899
+ throw new Error("Branch URL disappeared after validation");
900
+ }
901
+ process.stdout.write(url + "\n");
902
+ return;
903
+ }
667
904
  const current = getCurrentBranch();
668
905
  const marker = branch.name === current ? " (current)" : "";
669
906
  console.log(`Branch: ${branch.name}${marker}
@@ -675,7 +912,6 @@ function infoAction(name) {
675
912
  console.log(`
676
913
  URL: ${branch.branch_url}`);
677
914
  }
678
- trackEvent("CLI: branch info");
679
915
  }
680
916
 
681
917
  // src/commands/branch/delete.ts
@@ -770,9 +1006,9 @@ ${yellow}To view the current state of a branch:${reset2} \`ardent branch info <n
770
1006
 
771
1007
  // src/commands/branch/index.ts
772
1008
  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);
1009
+ 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
1010
  branchCommand.command("list").description("List your branches").action(listAction);
775
- branchCommand.command("info [name]").description("Show branch details (defaults to current branch)").action(infoAction);
1011
+ 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
1012
  branchCommand.command("delete <name>").description("Delete a branch").action(deleteAction);
777
1013
  branchCommand.command("switch <name>").description("Switch to a different branch").action(switchAction);
778
1014
  branchCommand.command("diff [name]", { hidden: true }).description("(removed) Show changes on a branch").action(diffAction);
@@ -1786,6 +2022,9 @@ async function createAction2(type, url, options) {
1786
2022
  if (parsed.ssl_mode !== void 0) {
1787
2023
  connectionDetails.ssl_mode = parsed.ssl_mode;
1788
2024
  }
2025
+ if (parsed.database !== void 0) {
2026
+ connectionDetails.database = parsed.database;
2027
+ }
1789
2028
  createPayload = {
1790
2029
  name: connectorName,
1791
2030
  service_name: "postgresql",
@@ -4632,14 +4871,14 @@ function logoutAction() {
4632
4871
 
4633
4872
  // src/commands/auth/status.ts
4634
4873
  function statusAction() {
4635
- const token = getConfig("token");
4874
+ const token = getToken();
4636
4875
  if (!token) {
4637
4876
  trackEvent("CLI: auth status", { authenticated: false });
4638
4877
  console.log("\u2717 Not authenticated");
4639
4878
  console.log(" Run: ardent login");
4640
4879
  return;
4641
4880
  }
4642
- const user = getConfig("user");
4881
+ const user = getConfig("token") ? getConfig("user") : void 0;
4643
4882
  console.log("\u2713 Authenticated");
4644
4883
  if (user?.full_name) {
4645
4884
  console.log(` Account: ${user.full_name}`);
@@ -4658,7 +4897,12 @@ function statusAction() {
4658
4897
  }
4659
4898
 
4660
4899
  // src/commands/auth/index.ts
4661
- var loginCommand = new Command7("login").description("Login to Ardent").option("-t, --token <token>", "API token (skip browser login)").action(loginAction);
4900
+ var loginCommand = new Command7("login").description(
4901
+ "Login to Ardent. Defaults to GitHub OAuth in the browser; pass --token for headless auth, or set the ARDENT_TOKEN env var to skip 'ardent login' entirely."
4902
+ ).option(
4903
+ "-t, --token <token>",
4904
+ "API token for headless auth (skip browser login). For repeat headless use, prefer the ARDENT_TOKEN env var."
4905
+ ).action(loginAction);
4662
4906
  var logoutCommand = new Command7("logout").description("Logout from Ardent").action(logoutAction);
4663
4907
  var statusCommand = new Command7("status").description("Show status").action(statusAction);
4664
4908
 
@@ -4744,6 +4988,7 @@ USAGE
4744
4988
 
4745
4989
  AUTHENTICATION
4746
4990
  login Login via browser (GitHub OAuth)
4991
+ login --token <token> Login with an API token (headless, no browser)
4747
4992
  logout Clear stored credentials
4748
4993
  status Check authentication status
4749
4994
 
@@ -4785,12 +5030,29 @@ OPTIONS
4785
5030
  --help Show help for any command
4786
5031
  --version Show CLI version
4787
5032
 
5033
+ ENVIRONMENT
5034
+ ARDENT_TOKEN API token used for every request. When set, no
5035
+ 'ardent login' is needed \u2014 ideal for CI and other
5036
+ headless contexts. Generate one in the web app
5037
+ under Settings > API Keys.
5038
+ ARDENT_API_URL Override the API endpoint
5039
+ (default: https://api.tryardent.com).
5040
+
4788
5041
  EXAMPLES
4789
5042
  ardent login
4790
5043
  ardent connector create postgresql postgresql://user:password@your-db-host.com:5432/database
4791
5044
  ardent branch create my-feature
4792
5045
  ardent branch switch my-feature
4793
5046
  ardent org members
5047
+
5048
+ # Headless / CI (GitHub Actions)
5049
+ # env:
5050
+ # ARDENT_TOKEN: \${{ secrets.ARDENT_TOKEN }}
5051
+ # run: |
5052
+ # BRANCH="pr-\${{ github.event.number }}-\${{ github.run_id }}"
5053
+ # ardent branch create "$BRANCH"
5054
+ # # ... run your migration / tests against the branch ...
5055
+ # ardent branch delete "$BRANCH"
4794
5056
  `;
4795
5057
  var program = new Command8();
4796
5058
  program.name("ardent").description("CLI for Ardent database branching").version(CLI_VERSION).configureHelp({
@@ -4825,5 +5087,7 @@ if (args.length === 0) {
4825
5087
  program.help();
4826
5088
  }
4827
5089
  getAnonymousId();
4828
- checkForUpdate(CLI_VERSION);
5090
+ if (!isMachineReadableBranchInvocation(args)) {
5091
+ checkForUpdate(CLI_VERSION);
5092
+ }
4829
5093
  program.parse();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ardent-cli",
3
- "version": "0.0.42",
3
+ "version": "0.0.44",
4
4
  "description": "Git for Data infrastructure",
5
5
  "type": "module",
6
6
  "bin": {