ardent-cli 0.0.44 → 0.0.46
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 +196 -24
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -478,6 +478,37 @@ async function resolveCurrentConnectorId() {
|
|
|
478
478
|
);
|
|
479
479
|
}
|
|
480
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
|
+
}
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
|
|
481
512
|
// src/lib/telemetry.ts
|
|
482
513
|
import { randomUUID } from "crypto";
|
|
483
514
|
function getAnonymousId() {
|
|
@@ -626,6 +657,21 @@ async function createAction(name, options) {
|
|
|
626
657
|
process.exit(2);
|
|
627
658
|
}
|
|
628
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
|
+
}
|
|
629
675
|
try {
|
|
630
676
|
const startTime = performance.now();
|
|
631
677
|
const connectorId = await resolveCurrentConnectorId();
|
|
@@ -1865,12 +1911,85 @@ async function handleReplicaIdentityPreflight(connectorId, options, behavior = {
|
|
|
1865
1911
|
return { submitted: true, preflight: refreshed };
|
|
1866
1912
|
}
|
|
1867
1913
|
|
|
1914
|
+
// src/lib/connector_name.ts
|
|
1915
|
+
var SUFFIX_RESERVE = 5;
|
|
1916
|
+
var MAX_BASE_LENGTH = MAX_RESOURCE_NAME_LENGTH - SUFFIX_RESERVE;
|
|
1917
|
+
var MAX_COLLISION_SUFFIX = 1e3;
|
|
1918
|
+
function slugifyToResourceName(input) {
|
|
1919
|
+
const slug = input.normalize("NFKD").replace(new RegExp("\\p{Mn}", "gu"), "").toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, MAX_BASE_LENGTH).replace(/-+$/g, "");
|
|
1920
|
+
if (slug.length === 0) {
|
|
1921
|
+
return null;
|
|
1922
|
+
}
|
|
1923
|
+
try {
|
|
1924
|
+
validateResourceName(slug);
|
|
1925
|
+
} catch {
|
|
1926
|
+
return null;
|
|
1927
|
+
}
|
|
1928
|
+
return slug;
|
|
1929
|
+
}
|
|
1930
|
+
function deriveConnectorName(options) {
|
|
1931
|
+
let base = null;
|
|
1932
|
+
for (const source of options.sources) {
|
|
1933
|
+
if (!source) {
|
|
1934
|
+
continue;
|
|
1935
|
+
}
|
|
1936
|
+
const candidate = slugifyToResourceName(source);
|
|
1937
|
+
if (candidate) {
|
|
1938
|
+
base = candidate;
|
|
1939
|
+
break;
|
|
1940
|
+
}
|
|
1941
|
+
}
|
|
1942
|
+
if (base === null) {
|
|
1943
|
+
base = slugifyToResourceName(options.fallbackBase);
|
|
1944
|
+
}
|
|
1945
|
+
if (base === null) {
|
|
1946
|
+
throw new Error(
|
|
1947
|
+
`Could not derive a connector name: fallback base '${options.fallbackBase}' is not a valid resource name`
|
|
1948
|
+
);
|
|
1949
|
+
}
|
|
1950
|
+
const taken = new Set(options.existingNames);
|
|
1951
|
+
if (!taken.has(base)) {
|
|
1952
|
+
return base;
|
|
1953
|
+
}
|
|
1954
|
+
for (let suffix = 2; suffix <= MAX_COLLISION_SUFFIX; suffix++) {
|
|
1955
|
+
const candidate = `${base}-${suffix}`;
|
|
1956
|
+
if (!taken.has(candidate)) {
|
|
1957
|
+
return candidate;
|
|
1958
|
+
}
|
|
1959
|
+
}
|
|
1960
|
+
throw new Error(
|
|
1961
|
+
`Could not derive a unique connector name from base '${base}' after ${MAX_COLLISION_SUFFIX} attempts; pass --name explicitly`
|
|
1962
|
+
);
|
|
1963
|
+
}
|
|
1964
|
+
|
|
1868
1965
|
// src/commands/connector/create.ts
|
|
1869
1966
|
function printEngineSetupRecoveryHint(connectorName) {
|
|
1870
1967
|
console.error("\u2717 Engine setup did not complete for this connector.");
|
|
1871
1968
|
console.error(" Inspect: ardent connector list");
|
|
1872
1969
|
console.error(` Retry: ardent connector retry-setup ${connectorName}`);
|
|
1873
1970
|
}
|
|
1971
|
+
async function deriveDefaultConnectorName(input) {
|
|
1972
|
+
let existingNames;
|
|
1973
|
+
try {
|
|
1974
|
+
const existing = await api.get(
|
|
1975
|
+
`/v1/cli/connectors?project_id=${input.projectId}`
|
|
1976
|
+
);
|
|
1977
|
+
existingNames = existing.connectors.map((connector) => connector.name);
|
|
1978
|
+
} catch (listErr) {
|
|
1979
|
+
if (isPermissionError(listErr)) {
|
|
1980
|
+
throw listErr;
|
|
1981
|
+
}
|
|
1982
|
+
const detail = listErr instanceof Error ? listErr.message : String(listErr);
|
|
1983
|
+
throw new Error(
|
|
1984
|
+
`Could not look up existing connectors to choose a default name (${detail}). Re-run with --name <name>.`
|
|
1985
|
+
);
|
|
1986
|
+
}
|
|
1987
|
+
return deriveConnectorName({
|
|
1988
|
+
sources: [input.projectName, input.sourceDatabase, input.sourceHost],
|
|
1989
|
+
existingNames,
|
|
1990
|
+
fallbackBase: input.serviceType
|
|
1991
|
+
});
|
|
1992
|
+
}
|
|
1874
1993
|
async function promptForUnsupportedExtensions(connectorId, unsupported, alreadyPersisted) {
|
|
1875
1994
|
function mergeAllowlist(newlyAccepted) {
|
|
1876
1995
|
return Array.from(/* @__PURE__ */ new Set([...alreadyPersisted, ...newlyAccepted])).sort();
|
|
@@ -1981,7 +2100,16 @@ async function createAction2(type, url, options) {
|
|
|
1981
2100
|
process.exit(1);
|
|
1982
2101
|
}
|
|
1983
2102
|
try {
|
|
1984
|
-
|
|
2103
|
+
if (options.name) {
|
|
2104
|
+
try {
|
|
2105
|
+
validateResourceName(options.name);
|
|
2106
|
+
} catch (validationError) {
|
|
2107
|
+
const message = validationError instanceof Error ? validationError.message : String(validationError);
|
|
2108
|
+
trackEvent("CLI: connector create failed", { reason: "invalid_name" });
|
|
2109
|
+
console.error(`\u2717 ${message}`);
|
|
2110
|
+
process.exit(1);
|
|
2111
|
+
}
|
|
2112
|
+
}
|
|
1985
2113
|
const currentProjectId = getConfig("currentProjectId");
|
|
1986
2114
|
if (!currentProjectId) {
|
|
1987
2115
|
console.error("\u2717 No current project set. Switch to a project first:");
|
|
@@ -1989,6 +2117,22 @@ async function createAction2(type, url, options) {
|
|
|
1989
2117
|
console.error(" ardent project switch <name>");
|
|
1990
2118
|
process.exit(1);
|
|
1991
2119
|
}
|
|
2120
|
+
let parsedUrl;
|
|
2121
|
+
if (!isByocNeon) {
|
|
2122
|
+
parsedUrl = parsePostgresUrl(url);
|
|
2123
|
+
if (!parsedUrl.password) {
|
|
2124
|
+
console.error("\u2717 Password required in connection URL");
|
|
2125
|
+
console.error(" Example: postgresql://user:password@host:5432/db");
|
|
2126
|
+
process.exit(1);
|
|
2127
|
+
}
|
|
2128
|
+
}
|
|
2129
|
+
const connectorName = options.name ? options.name : await deriveDefaultConnectorName({
|
|
2130
|
+
projectId: currentProjectId,
|
|
2131
|
+
projectName: getConfig("currentProjectName"),
|
|
2132
|
+
sourceDatabase: parsedUrl?.database,
|
|
2133
|
+
sourceHost: parsedUrl?.host,
|
|
2134
|
+
serviceType: type.toLowerCase()
|
|
2135
|
+
});
|
|
1992
2136
|
let createPayload;
|
|
1993
2137
|
if (isByocNeon) {
|
|
1994
2138
|
console.log(
|
|
@@ -2004,12 +2148,7 @@ async function createAction2(type, url, options) {
|
|
|
2004
2148
|
connection_details: {}
|
|
2005
2149
|
};
|
|
2006
2150
|
} else {
|
|
2007
|
-
const parsed =
|
|
2008
|
-
if (!parsed.password) {
|
|
2009
|
-
console.error("\u2717 Password required in connection URL");
|
|
2010
|
-
console.error(" Example: postgresql://user:password@host:5432/db");
|
|
2011
|
-
process.exit(1);
|
|
2012
|
-
}
|
|
2151
|
+
const parsed = parsedUrl;
|
|
2013
2152
|
console.log(
|
|
2014
2153
|
`Creating connector${useByocEnvironment ? " (BYOC environment)" : ""}...`
|
|
2015
2154
|
);
|
|
@@ -2166,6 +2305,7 @@ async function createAction2(type, url, options) {
|
|
|
2166
2305
|
});
|
|
2167
2306
|
if (isDegraded) {
|
|
2168
2307
|
console.log("\u2713 Connector created");
|
|
2308
|
+
console.log(` Name: ${connectorName}`);
|
|
2169
2309
|
console.log(` ID: ${connectorId}`);
|
|
2170
2310
|
console.log("");
|
|
2171
2311
|
let warnings = [];
|
|
@@ -2179,6 +2319,7 @@ async function createAction2(type, url, options) {
|
|
|
2179
2319
|
printDegradedWarnings(warnings);
|
|
2180
2320
|
} else {
|
|
2181
2321
|
console.log("\u2713 Connector created and ready");
|
|
2322
|
+
console.log(` Name: ${connectorName}`);
|
|
2182
2323
|
console.log(` ID: ${connectorId}`);
|
|
2183
2324
|
}
|
|
2184
2325
|
showNextStep();
|
|
@@ -2231,18 +2372,27 @@ function renderConnectorIcon(connector) {
|
|
|
2231
2372
|
}
|
|
2232
2373
|
|
|
2233
2374
|
// src/commands/connector/list.ts
|
|
2375
|
+
function printOtherProjectsHint(summary, currentProjectHasConnectors, dim2, reset2) {
|
|
2376
|
+
if (!summary) return;
|
|
2377
|
+
const connectorWord = summary.connectorCount === 1 ? "connector" : "connectors";
|
|
2378
|
+
const projectWord = summary.projectCount === 1 ? "project" : "projects";
|
|
2379
|
+
const countPhrase = currentProjectHasConnectors ? `${summary.connectorCount} more ${connectorWord}` : `${summary.connectorCount} ${connectorWord}`;
|
|
2380
|
+
console.log(
|
|
2381
|
+
`${dim2}more: ${countPhrase} across ${summary.projectCount} other ${projectWord} \u2014 switch with: ardent project switch <name>${reset2}`
|
|
2382
|
+
);
|
|
2383
|
+
}
|
|
2234
2384
|
async function listAction2() {
|
|
2385
|
+
const currentProjectId = getConfig("currentProjectId");
|
|
2386
|
+
if (!currentProjectId) {
|
|
2387
|
+
console.error("\u2717 No current project set. Switch to a project first:");
|
|
2388
|
+
console.error(" ardent project list");
|
|
2389
|
+
console.error(" ardent project switch <name>");
|
|
2390
|
+
process.exit(1);
|
|
2391
|
+
}
|
|
2235
2392
|
let connectors = [];
|
|
2236
2393
|
let fromCache = false;
|
|
2237
2394
|
let cacheTime = "";
|
|
2238
2395
|
try {
|
|
2239
|
-
const currentProjectId = getConfig("currentProjectId");
|
|
2240
|
-
if (!currentProjectId) {
|
|
2241
|
-
console.error("\u2717 No current project set. Switch to a project first:");
|
|
2242
|
-
console.error(" ardent project list");
|
|
2243
|
-
console.error(" ardent project switch <name>");
|
|
2244
|
-
process.exit(1);
|
|
2245
|
-
}
|
|
2246
2396
|
const result = await api.get(`/v1/cli/connectors?project_id=${currentProjectId}`);
|
|
2247
2397
|
if (!result.connectors) {
|
|
2248
2398
|
throw new Error("API returned invalid response: missing connectors array");
|
|
@@ -2267,26 +2417,47 @@ async function listAction2() {
|
|
|
2267
2417
|
process.exit(1);
|
|
2268
2418
|
}
|
|
2269
2419
|
}
|
|
2420
|
+
let otherProjectsSummary = null;
|
|
2421
|
+
if (!fromCache) {
|
|
2422
|
+
try {
|
|
2423
|
+
const all = await api.get("/v1/cli/connectors");
|
|
2424
|
+
const others = (all.connectors ?? []).filter(
|
|
2425
|
+
(connector) => connector.project_id && connector.project_id !== currentProjectId
|
|
2426
|
+
);
|
|
2427
|
+
if (others.length > 0) {
|
|
2428
|
+
otherProjectsSummary = {
|
|
2429
|
+
connectorCount: others.length,
|
|
2430
|
+
projectCount: new Set(others.map((connector) => connector.project_id)).size
|
|
2431
|
+
};
|
|
2432
|
+
}
|
|
2433
|
+
} catch {
|
|
2434
|
+
otherProjectsSummary = null;
|
|
2435
|
+
}
|
|
2436
|
+
}
|
|
2270
2437
|
trackEvent("CLI: connector list succeeded", { connector_count: connectors.length, from_cache: fromCache });
|
|
2271
2438
|
if (fromCache) {
|
|
2272
2439
|
console.log(`\u26A0 Offline - showing cached data from ${cacheTime}
|
|
2273
2440
|
`);
|
|
2274
2441
|
}
|
|
2275
|
-
if (connectors.length === 0) {
|
|
2276
|
-
const green3 = "\x1B[32m";
|
|
2277
|
-
const reset3 = "\x1B[0m";
|
|
2278
|
-
console.log("No connectors found");
|
|
2279
|
-
console.log(`${green3} Create one with: ardent connector create postgresql <url>${reset3}`);
|
|
2280
|
-
return;
|
|
2281
|
-
}
|
|
2282
|
-
const selectedConnector = fromCache ? void 0 : reconcileSelectedConnector(connectors);
|
|
2283
|
-
const currentConnectorId = selectedConnector?.id ?? getConfig("currentConnectorId");
|
|
2284
2442
|
const green2 = "\x1B[32m";
|
|
2285
2443
|
const dim2 = "\x1B[2m";
|
|
2286
2444
|
const yellow = "\x1B[33m";
|
|
2287
2445
|
const red = "\x1B[31m";
|
|
2288
2446
|
const reset2 = "\x1B[0m";
|
|
2289
|
-
|
|
2447
|
+
const projectLabel = getConfig("currentProjectName") ?? currentProjectId;
|
|
2448
|
+
console.log(`Project: ${projectLabel}
|
|
2449
|
+
`);
|
|
2450
|
+
if (connectors.length === 0) {
|
|
2451
|
+
console.log(" No connectors in this project.");
|
|
2452
|
+
console.log(`${green2} Create one with: ardent connector create postgresql <url>${reset2}`);
|
|
2453
|
+
if (otherProjectsSummary) {
|
|
2454
|
+
console.log("");
|
|
2455
|
+
printOtherProjectsHint(otherProjectsSummary, false, dim2, reset2);
|
|
2456
|
+
}
|
|
2457
|
+
return;
|
|
2458
|
+
}
|
|
2459
|
+
const selectedConnector = fromCache ? void 0 : reconcileSelectedConnector(connectors);
|
|
2460
|
+
const currentConnectorId = selectedConnector?.id ?? getConfig("currentConnectorId");
|
|
2290
2461
|
let enginePendingCount = 0;
|
|
2291
2462
|
for (const connector of connectors) {
|
|
2292
2463
|
const isCurrent = connector.id === currentConnectorId;
|
|
@@ -2322,6 +2493,7 @@ async function listAction2() {
|
|
|
2322
2493
|
connector_count: connectors.length
|
|
2323
2494
|
});
|
|
2324
2495
|
}
|
|
2496
|
+
printOtherProjectsHint(otherProjectsSummary, true, dim2, reset2);
|
|
2325
2497
|
}
|
|
2326
2498
|
|
|
2327
2499
|
// src/commands/connector/delete.ts
|