ardent-cli 0.0.28 → 0.0.29

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 +220 -0
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -343,6 +343,15 @@ var ApiClient = class {
343
343
  });
344
344
  return this.handleResponse(response);
345
345
  }
346
+ async put(path, body) {
347
+ const url = `${getApiUrl()}${path}`;
348
+ const response = await fetch(url, {
349
+ method: "PUT",
350
+ headers: this.getHeaders(),
351
+ body: JSON.stringify(body)
352
+ });
353
+ return this.handleResponse(response);
354
+ }
346
355
  };
347
356
  var api = new ApiClient();
348
357
  function isNetworkError(err) {
@@ -987,6 +996,52 @@ Example:
987
996
  }
988
997
  }
989
998
 
999
+ // src/lib/prompt.ts
1000
+ import { createInterface } from "readline/promises";
1001
+ function isTtyInteractive() {
1002
+ return Boolean(process.stdin.isTTY && process.stdout.isTTY);
1003
+ }
1004
+ async function readOneLine(question) {
1005
+ const rl = createInterface({
1006
+ input: process.stdin,
1007
+ output: process.stdout
1008
+ });
1009
+ try {
1010
+ const answer = await rl.question(question);
1011
+ return answer.trim();
1012
+ } finally {
1013
+ rl.close();
1014
+ }
1015
+ }
1016
+ async function confirm(question, options = {}) {
1017
+ if (!isTtyInteractive()) {
1018
+ return false;
1019
+ }
1020
+ const defaultYes = options.defaultYes ?? true;
1021
+ const suffix = defaultYes ? "[Y/n] " : "[y/N] ";
1022
+ const answer = (await readOneLine(`${question} ${suffix}`)).toLowerCase();
1023
+ if (answer === "") return defaultYes;
1024
+ if (answer === "y" || answer === "yes") return true;
1025
+ if (answer === "n" || answer === "no") return false;
1026
+ const retry = (await readOneLine(`Please answer yes or no. ${suffix}`)).toLowerCase();
1027
+ if (retry === "") return defaultYes;
1028
+ if (retry === "y" || retry === "yes") return true;
1029
+ if (retry === "n" || retry === "no") return false;
1030
+ console.log('Unrecognised input; treating as "no".');
1031
+ return false;
1032
+ }
1033
+ async function multiSelect(items, promptForItem, options = {}) {
1034
+ if (!isTtyInteractive()) {
1035
+ return [];
1036
+ }
1037
+ const accepted = [];
1038
+ for (const item of items) {
1039
+ const yes = await confirm(promptForItem(item), { defaultYes: options.defaultYes });
1040
+ if (yes) accepted.push(item);
1041
+ }
1042
+ return accepted;
1043
+ }
1044
+
990
1045
  // src/commands/connector/create.ts
991
1046
  function parsePostgresUrl(url) {
992
1047
  const atIndex = url.lastIndexOf("@");
@@ -1007,6 +1062,57 @@ function printEngineSetupRecoveryHint(connectorName) {
1007
1062
  console.error(" Inspect: ardent connector list");
1008
1063
  console.error(` Retry: ardent connector retry-setup ${connectorName}`);
1009
1064
  }
1065
+ async function promptForUnsupportedExtensions(connectorId, unsupported, alreadyPersisted) {
1066
+ function mergeAllowlist(newlyAccepted) {
1067
+ return Array.from(/* @__PURE__ */ new Set([...alreadyPersisted, ...newlyAccepted])).sort();
1068
+ }
1069
+ console.log("");
1070
+ if (unsupported.length === 1) {
1071
+ const ext = unsupported[0];
1072
+ console.log(
1073
+ `Found 1 extension on your source database that isn't supported on branches:`
1074
+ );
1075
+ console.log(` \u2022 ${ext}`);
1076
+ console.log("");
1077
+ console.log(
1078
+ "Branches from this connector will fail unless this extension is excluded."
1079
+ );
1080
+ const yes = await confirm(`Branch without ${ext}?`, { defaultYes: true });
1081
+ if (!yes) return "aborted";
1082
+ const merged2 = mergeAllowlist([ext]);
1083
+ await api.put(`/v1/connectors/${connectorId}`, {
1084
+ drop_extensions: merged2
1085
+ });
1086
+ console.log(`\u2713 Saved drop selection: ${merged2.join(", ")}`);
1087
+ return "applied";
1088
+ }
1089
+ console.log(
1090
+ `Found ${unsupported.length} extensions on your source database that aren't supported on branches:`
1091
+ );
1092
+ for (const ext of unsupported) {
1093
+ console.log(` \u2022 ${ext}`);
1094
+ }
1095
+ console.log("");
1096
+ console.log(
1097
+ "Pick which extensions to exclude from branches. Branches from this connector"
1098
+ );
1099
+ console.log(
1100
+ "will fail unless every unsupported extension you keep is configured by support."
1101
+ );
1102
+ console.log("");
1103
+ const accepted = await multiSelect(
1104
+ unsupported,
1105
+ (ext) => `Branch without ${ext}?`,
1106
+ { defaultYes: true }
1107
+ );
1108
+ if (accepted.length === 0) return "aborted";
1109
+ const merged = mergeAllowlist(accepted);
1110
+ await api.put(`/v1/connectors/${connectorId}`, {
1111
+ drop_extensions: merged
1112
+ });
1113
+ console.log(`\u2713 Saved drop selection: ${merged.join(", ")}`);
1114
+ return "applied";
1115
+ }
1010
1116
  async function createAction2(type, url, options) {
1011
1117
  const supportedTypes = ["postgresql"];
1012
1118
  if (!supportedTypes.includes(type.toLowerCase())) {
@@ -1145,6 +1251,36 @@ async function createAction2(type, url, options) {
1145
1251
  selected_paths: ["*"]
1146
1252
  });
1147
1253
  const connector = await api.get(`/v1/connectors/${connectorId}`);
1254
+ const preview = connector.unsupported_extensions_preview ?? null;
1255
+ const unsupportedExtensions = preview?.unsupported ?? [];
1256
+ const dropExtensionsPersisted = preview?.drop_extensions_persisted ?? [];
1257
+ if (unsupportedExtensions.length > 0) {
1258
+ const promptOutcome = await promptForUnsupportedExtensions(
1259
+ connectorId,
1260
+ unsupportedExtensions,
1261
+ dropExtensionsPersisted
1262
+ );
1263
+ if (promptOutcome === "aborted") {
1264
+ trackEvent("CLI: connector create aborted", {
1265
+ reason: "declined_extension_drops",
1266
+ unsupported_count: unsupportedExtensions.length
1267
+ });
1268
+ console.error(
1269
+ "\u2717 Connector setup aborted because no extension drop selection was made."
1270
+ );
1271
+ console.error(
1272
+ " Branches cannot be created until the connector has a drop selection."
1273
+ );
1274
+ console.error(" Run this when ready:");
1275
+ console.error(
1276
+ ` ardent connector update ${connectorName} --drop-extensions <ext,...>`
1277
+ );
1278
+ console.error(
1279
+ ` ardent connector retry-setup ${connectorName}`
1280
+ );
1281
+ process.exit(1);
1282
+ }
1283
+ }
1148
1284
  if (connector.branching_engine_status === "configuration_verified") {
1149
1285
  console.log("Setting up branching engine...");
1150
1286
  try {
@@ -1535,6 +1671,86 @@ async function switchAction2(name) {
1535
1671
  trackEvent("CLI: connector switch");
1536
1672
  }
1537
1673
 
1674
+ // src/lib/drop_extensions.ts
1675
+ function parseDropExtensions(raw) {
1676
+ if (raw === void 0) return null;
1677
+ if (raw.trim() === "") return [];
1678
+ return raw.split(",").map((entry) => entry.trim()).filter((entry) => entry.length > 0);
1679
+ }
1680
+
1681
+ // src/commands/connector/update.ts
1682
+ async function resolveConnectorByName(name) {
1683
+ const cached = getCacheEntry("connectors");
1684
+ let connector = cached?.data.find((c) => c.name === name);
1685
+ if (!connector) {
1686
+ try {
1687
+ const result = await api.get(
1688
+ "/v1/cli/connectors"
1689
+ );
1690
+ if (result.connectors) {
1691
+ setCacheEntry("connectors", result.connectors);
1692
+ connector = result.connectors.find((c) => c.name === name);
1693
+ }
1694
+ } catch (err) {
1695
+ if (isNetworkError(err)) {
1696
+ console.error("\u2717 Connector not found in cache and offline");
1697
+ process.exit(1);
1698
+ }
1699
+ throw err;
1700
+ }
1701
+ }
1702
+ if (!connector) {
1703
+ console.error(`\u2717 Connector "${name}" not found`);
1704
+ console.log(" Run: ardent connector list");
1705
+ process.exit(1);
1706
+ }
1707
+ return connector;
1708
+ }
1709
+ async function updateAction(name, options = {}) {
1710
+ const dropExtensions = parseDropExtensions(options.dropExtensions);
1711
+ if (dropExtensions === null) {
1712
+ console.error("\u2717 Nothing to update.");
1713
+ console.error(" Try: ardent connector update <name> --drop-extensions <ext,...>");
1714
+ process.exit(1);
1715
+ }
1716
+ const connector = await resolveConnectorByName(name);
1717
+ try {
1718
+ const payload = { drop_extensions: dropExtensions };
1719
+ await api.put(`/v1/connectors/${connector.id}`, payload);
1720
+ trackEvent("CLI: connector update succeeded", {
1721
+ drop_extensions_count: dropExtensions.length
1722
+ });
1723
+ if (dropExtensions.length === 0) {
1724
+ console.log(`\u2713 Cleared extension drop allowlist on '${name}'`);
1725
+ } else {
1726
+ console.log(
1727
+ `\u2713 Updated extension drop allowlist on '${name}': ${dropExtensions.join(", ")}`
1728
+ );
1729
+ console.log(
1730
+ " These extensions will be omitted from future branches created from this connector."
1731
+ );
1732
+ }
1733
+ } catch (err) {
1734
+ if (isPermissionError(err)) {
1735
+ trackEvent("CLI: connector update failed", { reason: "permission_denied" });
1736
+ console.error("\u2717 You don't have permission to update this connector.");
1737
+ console.error("");
1738
+ console.error(" Ask your organization admin to either:");
1739
+ console.error(" \u2022 Update the connector for you, or");
1740
+ console.error(" \u2022 Upgrade your role to Admin");
1741
+ process.exit(1);
1742
+ }
1743
+ if (isNetworkError(err)) {
1744
+ trackEvent("CLI: connector update failed", { reason: "offline" });
1745
+ console.error("\u2717 Cannot update connector while offline");
1746
+ process.exit(1);
1747
+ }
1748
+ trackEvent("CLI: connector update failed", { reason: "api_error" });
1749
+ console.error("\u2717 Failed:", err instanceof Error ? err.message : err);
1750
+ process.exit(1);
1751
+ }
1752
+ }
1753
+
1538
1754
  // src/commands/connector/index.ts
1539
1755
  var connectorCommand = new Command2("connector").description("Manage database connectors");
1540
1756
  connectorCommand.command("create <type> [url]").description("Create a new connector").option("-n, --name <name>", "Connector name").option("--byoc <provider>", "Bring your own Neon project (e.g. neon)").option("--api-key <key>", "Neon API key (required with --byoc)").option("--project-id <id>", "Neon project ID (required with --byoc)").option(
@@ -1558,6 +1774,10 @@ connectorCommand.command("switch <name>").description("Switch to a different con
1558
1774
  connectorCommand.command("retry-setup <name>").description(
1559
1775
  "Retry branching engine setup for a connector that didn't finish (status: configuration_verified or validating)"
1560
1776
  ).action(retrySetupAction);
1777
+ connectorCommand.command("update <name>").description("Update a connector's configuration").option(
1778
+ "--drop-extensions <list>",
1779
+ "Comma-separated list of source extensions to drop on branches (e.g. pgmq,supabase_vault). Pass an empty string to clear the allowlist."
1780
+ ).action(updateAction);
1561
1781
  connectorCommand.command("delete <name>").description("Delete a connector by name").option(
1562
1782
  "--force",
1563
1783
  "Skip the in-flight WAL/Kafka drain wait. Any un-replicated changes are abandoned and recorded for operator triage."
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ardent-cli",
3
- "version": "0.0.28",
3
+ "version": "0.0.29",
4
4
  "description": "Git for Data infrastructure",
5
5
  "type": "module",
6
6
  "bin": {