ardent-cli 0.0.41 → 0.0.42

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 +251 -20
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -1247,6 +1247,43 @@ async function multiSelect(items, promptForItem, options = {}) {
1247
1247
  return accepted;
1248
1248
  }
1249
1249
 
1250
+ // src/lib/postgres_url.ts
1251
+ var VALID_SSL_MODES = /* @__PURE__ */ new Set([
1252
+ "disable",
1253
+ "prefer",
1254
+ "require"
1255
+ ]);
1256
+ function parsePostgresUrl(url) {
1257
+ const atIndex = url.lastIndexOf("@");
1258
+ const credentialsPart = atIndex > 0 ? url.substring(0, atIndex) : url;
1259
+ const hostPart = atIndex > 0 ? url.substring(atIndex) : "";
1260
+ const cleanedUrl = credentialsPart.replace(/\\!/g, "!") + hostPart;
1261
+ const parsed = new URL(cleanedUrl);
1262
+ const host = parsed.hostname;
1263
+ const port = parsed.port || "5432";
1264
+ const username = decodeURIComponent(parsed.username);
1265
+ const password = parsed.password ? decodeURIComponent(parsed.password) : "";
1266
+ const databasePath = parsed.pathname.replace(/^\/+/, "");
1267
+ const database = databasePath ? decodeURIComponent(databasePath) : void 0;
1268
+ if (!host) throw new Error("Host required in connection URL");
1269
+ if (!username) throw new Error("Username required in connection URL");
1270
+ const result = { host, port, username, password };
1271
+ if (database !== void 0) {
1272
+ result.database = database;
1273
+ }
1274
+ const sslMode = parsed.searchParams.get("sslmode");
1275
+ if (sslMode !== null) {
1276
+ const normalized = sslMode.trim().toLowerCase();
1277
+ if (!VALID_SSL_MODES.has(normalized)) {
1278
+ throw new Error(
1279
+ `Invalid sslmode '${sslMode}' in connection URL. Must be one of: ${Array.from(VALID_SSL_MODES).join(", ")}.`
1280
+ );
1281
+ }
1282
+ result.ssl_mode = normalized;
1283
+ }
1284
+ return result;
1285
+ }
1286
+
1250
1287
  // src/lib/replica_identity_prompt.ts
1251
1288
  import { createInterface as createInterface2 } from "readline/promises";
1252
1289
 
@@ -1593,20 +1630,6 @@ async function handleReplicaIdentityPreflight(connectorId, options, behavior = {
1593
1630
  }
1594
1631
 
1595
1632
  // src/commands/connector/create.ts
1596
- function parsePostgresUrl(url) {
1597
- const atIndex = url.lastIndexOf("@");
1598
- const credentialsPart = atIndex > 0 ? url.substring(0, atIndex) : url;
1599
- const hostPart = atIndex > 0 ? url.substring(atIndex) : "";
1600
- const cleanedUrl = credentialsPart.replace(/\\!/g, "!") + hostPart;
1601
- const parsed = new URL(cleanedUrl);
1602
- const host = parsed.hostname;
1603
- const port = parsed.port || "5432";
1604
- const username = decodeURIComponent(parsed.username);
1605
- const password = parsed.password ? decodeURIComponent(parsed.password) : "";
1606
- if (!host) throw new Error("Host required in connection URL");
1607
- if (!username) throw new Error("Username required in connection URL");
1608
- return { host, port, username, password };
1609
- }
1610
1633
  function printEngineSetupRecoveryHint(connectorName) {
1611
1634
  console.error("\u2717 Engine setup did not complete for this connector.");
1612
1635
  console.error(" Inspect: ardent connector list");
@@ -1754,16 +1777,20 @@ async function createAction2(type, url, options) {
1754
1777
  console.log(
1755
1778
  `Creating connector${useByocEnvironment ? " (BYOC environment)" : ""}...`
1756
1779
  );
1780
+ const connectionDetails = {
1781
+ host: parsed.host,
1782
+ port: parsed.port,
1783
+ username: parsed.username,
1784
+ password: parsed.password
1785
+ };
1786
+ if (parsed.ssl_mode !== void 0) {
1787
+ connectionDetails.ssl_mode = parsed.ssl_mode;
1788
+ }
1757
1789
  createPayload = {
1758
1790
  name: connectorName,
1759
1791
  service_name: "postgresql",
1760
1792
  project_id: currentProjectId,
1761
- connection_details: {
1762
- host: parsed.host,
1763
- port: parsed.port,
1764
- username: parsed.username,
1765
- password: parsed.password
1766
- }
1793
+ connection_details: connectionDetails
1767
1794
  };
1768
1795
  }
1769
1796
  if (useByocEnvironment) {
@@ -2122,6 +2149,195 @@ async function deleteAction2(name, options = {}) {
2122
2149
  }
2123
2150
  }
2124
2151
 
2152
+ // src/commands/connector/preflight.ts
2153
+ var CHECK_RENDER_ORDER = [
2154
+ "tcp_reachable",
2155
+ "auth",
2156
+ "postgres_metadata",
2157
+ "is_writer",
2158
+ "wal_level",
2159
+ "replication_slots",
2160
+ "wal_senders",
2161
+ "wal2json",
2162
+ "can_replicate",
2163
+ "can_read_tables",
2164
+ "can_write_tables",
2165
+ "can_modify_schema",
2166
+ "create_event_triggers",
2167
+ "duplicate_source"
2168
+ ];
2169
+ function isCheck(value) {
2170
+ return typeof value === "object" && value !== null && "pass" in value;
2171
+ }
2172
+ function renderCheck(name, check) {
2173
+ const mark = check.unverified ? "?" : check.pass ? "\u2713" : "\u2717";
2174
+ const label = name.replace(/_/g, " ");
2175
+ let line = ` ${mark} ${label}`;
2176
+ if (check.actual !== void 0) line += ` (actual: ${check.actual})`;
2177
+ if (check.max !== void 0 && check.used !== void 0) {
2178
+ line += ` (${check.used}/${check.max} used)`;
2179
+ } else if (check.max !== void 0) {
2180
+ line += ` (max: ${check.max})`;
2181
+ }
2182
+ if (!check.pass && !check.unverified && check.error)
2183
+ line += `
2184
+ ${check.error}`;
2185
+ if (check.unverified && check.error) line += `
2186
+ ${check.error}`;
2187
+ if (!check.pass && check.remediation) {
2188
+ if (check.remediation.message)
2189
+ line += `
2190
+ ${check.remediation.message}`;
2191
+ if (check.remediation.suggestion)
2192
+ line += `
2193
+ \u2192 ${check.remediation.suggestion}`;
2194
+ if (check.remediation.hint)
2195
+ line += `
2196
+ hint: ${check.remediation.hint}`;
2197
+ }
2198
+ if (check.existing_connector_id) {
2199
+ line += `
2200
+ existing connector: ${check.existing_connector_id}`;
2201
+ if (check.existing_connector_name)
2202
+ line += ` (${check.existing_connector_name})`;
2203
+ }
2204
+ return line;
2205
+ }
2206
+ function renderChecks(checks) {
2207
+ const rendered = [];
2208
+ const seen = /* @__PURE__ */ new Set();
2209
+ for (const name of CHECK_RENDER_ORDER) {
2210
+ const entry = checks[name];
2211
+ if (entry === void 0) continue;
2212
+ seen.add(name);
2213
+ if (!isCheck(entry)) continue;
2214
+ rendered.push(renderCheck(name, entry));
2215
+ }
2216
+ const SKIP = /* @__PURE__ */ new Set(["pass", "missing", "engine_config", "warnings"]);
2217
+ for (const [name, entry] of Object.entries(checks)) {
2218
+ if (seen.has(name) || SKIP.has(name)) continue;
2219
+ if (!isCheck(entry)) continue;
2220
+ rendered.push(renderCheck(name, entry));
2221
+ }
2222
+ return rendered;
2223
+ }
2224
+ function allChecksPass(checks) {
2225
+ for (const entry of Object.values(checks)) {
2226
+ if (!isCheck(entry)) continue;
2227
+ if (entry.unverified === true) continue;
2228
+ if (entry.pass !== true) return false;
2229
+ }
2230
+ return true;
2231
+ }
2232
+ async function preflightAction(type, url, options) {
2233
+ if (type !== "postgresql") {
2234
+ console.error(
2235
+ `\u2717 Preflight is currently only supported for postgresql connectors.`
2236
+ );
2237
+ console.error(` Got: ${type}`);
2238
+ process.exit(1);
2239
+ }
2240
+ if (!url) {
2241
+ console.error("\u2717 Connection URL required");
2242
+ console.error(
2243
+ " Example: ardent connector preflight postgresql postgresql://user:pass@host:5432/db"
2244
+ );
2245
+ process.exit(1);
2246
+ }
2247
+ let parsed;
2248
+ try {
2249
+ parsed = parsePostgresUrl(url);
2250
+ } catch (err) {
2251
+ const message = err instanceof Error ? err.message : String(err);
2252
+ console.error(`\u2717 ${message}`);
2253
+ process.exit(1);
2254
+ }
2255
+ if (!parsed.password) {
2256
+ console.error("\u2717 Password required in connection URL");
2257
+ process.exit(1);
2258
+ }
2259
+ if (options.environmentId && !options.byoc) {
2260
+ console.error("\u2717 --environment-id requires --byoc");
2261
+ process.exit(1);
2262
+ }
2263
+ if (options.privateLinkId && !options.byoc) {
2264
+ console.error("\u2717 --private-link-id requires --byoc");
2265
+ process.exit(1);
2266
+ }
2267
+ const connectionDetails = {
2268
+ host: parsed.host,
2269
+ port: parsed.port,
2270
+ username: parsed.username,
2271
+ password: parsed.password
2272
+ };
2273
+ if (parsed.ssl_mode !== void 0) {
2274
+ connectionDetails.ssl_mode = parsed.ssl_mode;
2275
+ }
2276
+ if (parsed.database !== void 0) {
2277
+ connectionDetails.database = parsed.database;
2278
+ }
2279
+ const payload = {
2280
+ service_name: "postgresql",
2281
+ connection_details: connectionDetails
2282
+ };
2283
+ if (parsed.database !== void 0) {
2284
+ payload.database = parsed.database;
2285
+ }
2286
+ if (options.byoc) {
2287
+ payload.use_environment = true;
2288
+ if (options.environmentId) payload.environment_id = options.environmentId;
2289
+ if (options.privateLinkId) payload.private_link_id = options.privateLinkId;
2290
+ }
2291
+ if (options.schemas) {
2292
+ payload.selected_schemas = options.schemas.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
2293
+ }
2294
+ console.log(`Running preflight against ${parsed.host}:${parsed.port}\u2026`);
2295
+ let response;
2296
+ try {
2297
+ response = await api.post(
2298
+ "/v1/connectors/preflight",
2299
+ payload
2300
+ );
2301
+ } catch (err) {
2302
+ const message = err instanceof Error ? err.message : String(err);
2303
+ console.error(`\u2717 Preflight request failed: ${message}`);
2304
+ process.exit(1);
2305
+ }
2306
+ console.log("");
2307
+ console.log("Connection checks:");
2308
+ for (const line of renderChecks(response.checks)) {
2309
+ console.log(line);
2310
+ }
2311
+ if (response.source_provider) {
2312
+ console.log("");
2313
+ console.log(`Detected source provider: ${response.source_provider}`);
2314
+ }
2315
+ if (response.grant_script) {
2316
+ console.log("");
2317
+ console.log("--- Grant script ---");
2318
+ console.log(response.grant_script.sql);
2319
+ }
2320
+ const overallPass = response.preflight_pass ?? (response.branching_prerequisites_pass && allChecksPass(response.checks));
2321
+ console.log("");
2322
+ if (overallPass) {
2323
+ console.log(
2324
+ "\u2713 Preflight checks passed. You're ready to create this connector."
2325
+ );
2326
+ } else {
2327
+ console.log(
2328
+ "\u2717 Preflight checks did NOT pass. Fix the failures above before creating."
2329
+ );
2330
+ }
2331
+ trackEvent("CLI: connector preflight", {
2332
+ source_provider: response.source_provider,
2333
+ branching_prerequisites_pass: response.branching_prerequisites_pass,
2334
+ preflight_pass: overallPass,
2335
+ byoc: Boolean(options.byoc),
2336
+ private_link: Boolean(options.privateLinkId)
2337
+ });
2338
+ process.exit(overallPass ? 0 : 2);
2339
+ }
2340
+
2125
2341
  // src/commands/connector/retry-setup.ts
2126
2342
  function connectorRetrySetupFailureTelemetry(err) {
2127
2343
  if (isPermissionError(err)) {
@@ -2375,6 +2591,21 @@ async function updateAction(name, options = {}) {
2375
2591
 
2376
2592
  // src/commands/connector/index.ts
2377
2593
  var connectorCommand = new Command2("connector").description("Manage database connectors");
2594
+ connectorCommand.command("preflight <type> [url]").description(
2595
+ "Validate a source database without persisting a connector row (no Key Vault write)"
2596
+ ).option(
2597
+ "--byoc",
2598
+ "Route preflight through the customer's BYOC environment (requires an active environment)"
2599
+ ).option(
2600
+ "--environment-id <id>",
2601
+ "BYOC environment to probe through (required with --byoc when you have more than one)"
2602
+ ).option(
2603
+ "--private-link-id <id>",
2604
+ "Private connection to use for the source database (requires --byoc)"
2605
+ ).option(
2606
+ "--schemas <list>",
2607
+ "Comma-separated schema names to include in the rendered grant script"
2608
+ ).action(preflightAction);
2378
2609
  connectorCommand.command("create <type> [url]").description("Create a new connector").option("-n, --name <name>", "Connector name").option(
2379
2610
  "--byoc [provider]",
2380
2611
  "Deploy connector on your BYOC environment, or legacy BYOC Neon provider form: --byoc neon"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ardent-cli",
3
- "version": "0.0.41",
3
+ "version": "0.0.42",
4
4
  "description": "Git for Data infrastructure",
5
5
  "type": "module",
6
6
  "bin": {